@bongos/core 1.19.252 → 1.19.254
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 +30 -20
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +11 -1
- package/docs/copy-inventory.md +37 -37
- package/docs/copy-registry.json +38 -38
- package/migrations/core_221_claims_requested_worktree_name.sql +144 -0
- package/modules/government/board.js +6 -1
- package/modules/hall-ui/public/government.js +34 -4
- package/modules/lifecycle/db.js +38 -10
- package/modules/lifecycle/routes/claims.js +20 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/run-unit-tests.js +8 -1
- package/src/module-api.js +5 -1
- package/tests/claims_requested_worktree_db.mjs +196 -0
- package/tests/government_charter_library.mjs +107 -1
- package/tests/one_active_claim_per_session.mjs +125 -0
package/docs/copy-registry.json
CHANGED
|
@@ -996,7 +996,7 @@
|
|
|
996
996
|
"occurrences": 2,
|
|
997
997
|
"at": [
|
|
998
998
|
"modules/hall-ui/public/gate.js:205",
|
|
999
|
-
"modules/hall-ui/public/government.js:
|
|
999
|
+
"modules/hall-ui/public/government.js:842"
|
|
1000
1000
|
]
|
|
1001
1001
|
},
|
|
1002
1002
|
{
|
|
@@ -1549,7 +1549,7 @@
|
|
|
1549
1549
|
"surface": "builders-hall",
|
|
1550
1550
|
"text": ", membership",
|
|
1551
1551
|
"file": "modules/hall-ui/public/government.js",
|
|
1552
|
-
"line":
|
|
1552
|
+
"line": 786,
|
|
1553
1553
|
"origin": "js-markup",
|
|
1554
1554
|
"confidence": "certain"
|
|
1555
1555
|
},
|
|
@@ -1855,7 +1855,7 @@
|
|
|
1855
1855
|
"surface": "builders-hall",
|
|
1856
1856
|
"text": "An objection returns the idea to its author with your reason and the section it faults \\u2014 the feedback they revise against.",
|
|
1857
1857
|
"file": "modules/hall-ui/public/government.js",
|
|
1858
|
-
"line":
|
|
1858
|
+
"line": 741,
|
|
1859
1859
|
"origin": "js-markup",
|
|
1860
1860
|
"confidence": "certain"
|
|
1861
1861
|
},
|
|
@@ -2026,7 +2026,7 @@
|
|
|
2026
2026
|
"surface": "builders-hall",
|
|
2027
2027
|
"text": "As configuration:",
|
|
2028
2028
|
"file": "modules/hall-ui/public/government.js",
|
|
2029
|
-
"line":
|
|
2029
|
+
"line": 633,
|
|
2030
2030
|
"origin": "js-markup",
|
|
2031
2031
|
"confidence": "certain"
|
|
2032
2032
|
},
|
|
@@ -2269,7 +2269,7 @@
|
|
|
2269
2269
|
"surface": "builders-hall",
|
|
2270
2270
|
"text": "Cast objection",
|
|
2271
2271
|
"file": "modules/hall-ui/public/government.js",
|
|
2272
|
-
"line":
|
|
2272
|
+
"line": 741,
|
|
2273
2273
|
"origin": "js-markup",
|
|
2274
2274
|
"confidence": "certain"
|
|
2275
2275
|
},
|
|
@@ -2332,7 +2332,7 @@
|
|
|
2332
2332
|
"surface": "builders-hall",
|
|
2333
2333
|
"text": "Charter library",
|
|
2334
2334
|
"file": "modules/hall-ui/public/government.js",
|
|
2335
|
-
"line":
|
|
2335
|
+
"line": 610,
|
|
2336
2336
|
"origin": "js-markup",
|
|
2337
2337
|
"confidence": "certain"
|
|
2338
2338
|
},
|
|
@@ -2566,7 +2566,7 @@
|
|
|
2566
2566
|
"surface": "builders-hall",
|
|
2567
2567
|
"text": "Closed sittings",
|
|
2568
2568
|
"file": "modules/hall-ui/public/government.js",
|
|
2569
|
-
"line":
|
|
2569
|
+
"line": 812,
|
|
2570
2570
|
"origin": "js-markup",
|
|
2571
2571
|
"confidence": "certain"
|
|
2572
2572
|
},
|
|
@@ -2998,7 +2998,7 @@
|
|
|
2998
2998
|
"surface": "builders-hall",
|
|
2999
2999
|
"text": "Could not load government data just now.",
|
|
3000
3000
|
"file": "modules/hall-ui/public/government.js",
|
|
3001
|
-
"line":
|
|
3001
|
+
"line": 883,
|
|
3002
3002
|
"origin": "js-markup",
|
|
3003
3003
|
"confidence": "certain"
|
|
3004
3004
|
},
|
|
@@ -3052,7 +3052,7 @@
|
|
|
3052
3052
|
"surface": "builders-hall",
|
|
3053
3053
|
"text": "Could not load the Board Room just now.",
|
|
3054
3054
|
"file": "modules/hall-ui/public/government.js",
|
|
3055
|
-
"line":
|
|
3055
|
+
"line": 804,
|
|
3056
3056
|
"origin": "js-markup",
|
|
3057
3057
|
"confidence": "certain"
|
|
3058
3058
|
},
|
|
@@ -3061,7 +3061,7 @@
|
|
|
3061
3061
|
"surface": "builders-hall",
|
|
3062
3062
|
"text": "Could not load the constitution just now.",
|
|
3063
3063
|
"file": "modules/hall-ui/public/government.js",
|
|
3064
|
-
"line":
|
|
3064
|
+
"line": 628,
|
|
3065
3065
|
"origin": "js-markup",
|
|
3066
3066
|
"confidence": "certain"
|
|
3067
3067
|
},
|
|
@@ -3457,7 +3457,7 @@
|
|
|
3457
3457
|
"surface": "builders-hall",
|
|
3458
3458
|
"text": "Could not vote: {…}",
|
|
3459
3459
|
"file": "modules/hall-ui/public/government.js",
|
|
3460
|
-
"line":
|
|
3460
|
+
"line": 768,
|
|
3461
3461
|
"origin": "js-notice",
|
|
3462
3462
|
"confidence": "certain"
|
|
3463
3463
|
},
|
|
@@ -4087,7 +4087,7 @@
|
|
|
4087
4087
|
"surface": "builders-hall",
|
|
4088
4088
|
"text": "Full Idea",
|
|
4089
4089
|
"file": "modules/hall-ui/public/government.js",
|
|
4090
|
-
"line":
|
|
4090
|
+
"line": 669,
|
|
4091
4091
|
"origin": "js-markup",
|
|
4092
4092
|
"confidence": "certain"
|
|
4093
4093
|
},
|
|
@@ -4348,7 +4348,7 @@
|
|
|
4348
4348
|
"surface": "builders-hall",
|
|
4349
4349
|
"text": "How a project moves between structures over time — summarized from the synthesis:",
|
|
4350
4350
|
"file": "modules/hall-ui/public/government.js",
|
|
4351
|
-
"line":
|
|
4351
|
+
"line": 610,
|
|
4352
4352
|
"origin": "js-markup",
|
|
4353
4353
|
"confidence": "certain"
|
|
4354
4354
|
},
|
|
@@ -4366,7 +4366,7 @@
|
|
|
4366
4366
|
"surface": "builders-hall",
|
|
4367
4367
|
"text": "How it has changed",
|
|
4368
4368
|
"file": "modules/hall-ui/public/government.js",
|
|
4369
|
-
"line":
|
|
4369
|
+
"line": 633,
|
|
4370
4370
|
"origin": "js-markup",
|
|
4371
4371
|
"confidence": "certain"
|
|
4372
4372
|
},
|
|
@@ -4429,7 +4429,7 @@
|
|
|
4429
4429
|
"surface": "builders-hall",
|
|
4430
4430
|
"text": "How this project is governed — the constitution it runs, the Board Room, and the ranks & permissions that administer it.",
|
|
4431
4431
|
"file": "modules/hall-ui/public/government.js",
|
|
4432
|
-
"line":
|
|
4432
|
+
"line": 870,
|
|
4433
4433
|
"origin": "js-text-assign",
|
|
4434
4434
|
"confidence": "certain"
|
|
4435
4435
|
},
|
|
@@ -5509,7 +5509,7 @@
|
|
|
5509
5509
|
"surface": "builders-hall",
|
|
5510
5510
|
"text": "No sitting has closed yet.",
|
|
5511
5511
|
"file": "modules/hall-ui/public/government.js",
|
|
5512
|
-
"line":
|
|
5512
|
+
"line": 813,
|
|
5513
5513
|
"origin": "js-markup",
|
|
5514
5514
|
"confidence": "certain"
|
|
5515
5515
|
},
|
|
@@ -5563,7 +5563,7 @@
|
|
|
5563
5563
|
"surface": "builders-hall",
|
|
5564
5564
|
"text": "No votes yet{…}.",
|
|
5565
5565
|
"file": "modules/hall-ui/public/government.js",
|
|
5566
|
-
"line":
|
|
5566
|
+
"line": 687,
|
|
5567
5567
|
"origin": "js-markup",
|
|
5568
5568
|
"confidence": "certain"
|
|
5569
5569
|
},
|
|
@@ -6292,7 +6292,7 @@
|
|
|
6292
6292
|
"surface": "builders-hall",
|
|
6293
6293
|
"text": "Ranks & Permissions is the Archon administration surface. The Constitution and the Board Room are yours to read.",
|
|
6294
6294
|
"file": "modules/hall-ui/public/government.js",
|
|
6295
|
-
"line":
|
|
6295
|
+
"line": 854,
|
|
6296
6296
|
"origin": "js-markup",
|
|
6297
6297
|
"confidence": "certain"
|
|
6298
6298
|
},
|
|
@@ -6733,7 +6733,7 @@
|
|
|
6733
6733
|
"surface": "builders-hall",
|
|
6734
6734
|
"text": "Restricted.",
|
|
6735
6735
|
"file": "modules/hall-ui/public/government.js",
|
|
6736
|
-
"line":
|
|
6736
|
+
"line": 842,
|
|
6737
6737
|
"origin": "js-text-assign",
|
|
6738
6738
|
"confidence": "certain"
|
|
6739
6739
|
},
|
|
@@ -7030,7 +7030,7 @@
|
|
|
7030
7030
|
"surface": "builders-hall",
|
|
7031
7031
|
"text": "Selectable today",
|
|
7032
7032
|
"file": "modules/hall-ui/public/government.js",
|
|
7033
|
-
"line":
|
|
7033
|
+
"line": 610,
|
|
7034
7034
|
"origin": "js-markup",
|
|
7035
7035
|
"confidence": "certain"
|
|
7036
7036
|
},
|
|
@@ -7624,7 +7624,7 @@
|
|
|
7624
7624
|
"surface": "builders-hall",
|
|
7625
7625
|
"text": "The developmental ladder: {…}. Formality is adopted only where informal coordination breaks.",
|
|
7626
7626
|
"file": "modules/hall-ui/public/government.js",
|
|
7627
|
-
"line":
|
|
7627
|
+
"line": 616,
|
|
7628
7628
|
"origin": "js-markup",
|
|
7629
7629
|
"confidence": "certain"
|
|
7630
7630
|
},
|
|
@@ -7795,7 +7795,7 @@
|
|
|
7795
7795
|
"surface": "builders-hall",
|
|
7796
7796
|
"text": "The seven government structures, for getting a team up to speed before deciding when to flip a switch.",
|
|
7797
7797
|
"file": "modules/hall-ui/public/government.js",
|
|
7798
|
-
"line":
|
|
7798
|
+
"line": 610,
|
|
7799
7799
|
"origin": "js-markup",
|
|
7800
7800
|
"confidence": "certain"
|
|
7801
7801
|
},
|
|
@@ -8020,7 +8020,7 @@
|
|
|
8020
8020
|
"surface": "builders-hall",
|
|
8021
8021
|
"text": "This is your idea \\u2014 its author does not vote on it. The board decides; a return comes back to you with its reason.",
|
|
8022
8022
|
"file": "modules/hall-ui/public/government.js",
|
|
8023
|
-
"line":
|
|
8023
|
+
"line": 737,
|
|
8024
8024
|
"origin": "js-markup",
|
|
8025
8025
|
"confidence": "certain"
|
|
8026
8026
|
},
|
|
@@ -8236,7 +8236,7 @@
|
|
|
8236
8236
|
"surface": "builders-hall",
|
|
8237
8237
|
"text": "Vote yes",
|
|
8238
8238
|
"file": "modules/hall-ui/public/government.js",
|
|
8239
|
-
"line":
|
|
8239
|
+
"line": 741,
|
|
8240
8240
|
"origin": "js-markup",
|
|
8241
8241
|
"confidence": "certain"
|
|
8242
8242
|
},
|
|
@@ -8371,7 +8371,7 @@
|
|
|
8371
8371
|
"surface": "builders-hall",
|
|
8372
8372
|
"text": "What this project runs today",
|
|
8373
8373
|
"file": "modules/hall-ui/public/government.js",
|
|
8374
|
-
"line":
|
|
8374
|
+
"line": 633,
|
|
8375
8375
|
"origin": "js-markup",
|
|
8376
8376
|
"confidence": "certain"
|
|
8377
8377
|
},
|
|
@@ -8479,7 +8479,7 @@
|
|
|
8479
8479
|
"surface": "builders-hall",
|
|
8480
8480
|
"text": "Why \\u2014 the reason the author will act on",
|
|
8481
8481
|
"file": "modules/hall-ui/public/government.js",
|
|
8482
|
-
"line":
|
|
8482
|
+
"line": 741,
|
|
8483
8483
|
"origin": "js-markup",
|
|
8484
8484
|
"confidence": "certain"
|
|
8485
8485
|
},
|
|
@@ -8623,7 +8623,7 @@
|
|
|
8623
8623
|
"surface": "builders-hall",
|
|
8624
8624
|
"text": "You have not voted on this item. Casting arrives with the vote form.",
|
|
8625
8625
|
"file": "modules/hall-ui/public/government.js",
|
|
8626
|
-
"line":
|
|
8626
|
+
"line": 785,
|
|
8627
8627
|
"origin": "js-markup",
|
|
8628
8628
|
"confidence": "certain"
|
|
8629
8629
|
},
|
|
@@ -8650,7 +8650,7 @@
|
|
|
8650
8650
|
"surface": "builders-hall",
|
|
8651
8651
|
"text": "You voted",
|
|
8652
8652
|
"file": "modules/hall-ui/public/government.js",
|
|
8653
|
-
"line":
|
|
8653
|
+
"line": 784,
|
|
8654
8654
|
"origin": "js-markup",
|
|
8655
8655
|
"confidence": "certain"
|
|
8656
8656
|
},
|
|
@@ -8956,7 +8956,7 @@
|
|
|
8956
8956
|
"surface": "builders-hall",
|
|
8957
8957
|
"text": "active builder{…}: {…}",
|
|
8958
8958
|
"file": "modules/hall-ui/public/government.js",
|
|
8959
|
-
"line":
|
|
8959
|
+
"line": 610,
|
|
8960
8960
|
"origin": "js-markup",
|
|
8961
8961
|
"confidence": "certain"
|
|
8962
8962
|
},
|
|
@@ -9037,7 +9037,7 @@
|
|
|
9037
9037
|
"surface": "builders-hall",
|
|
9038
9038
|
"text": "as configuration: {…}",
|
|
9039
9039
|
"file": "modules/hall-ui/public/government.js",
|
|
9040
|
-
"line":
|
|
9040
|
+
"line": 604,
|
|
9041
9041
|
"origin": "js-markup",
|
|
9042
9042
|
"confidence": "certain"
|
|
9043
9043
|
},
|
|
@@ -9181,7 +9181,7 @@
|
|
|
9181
9181
|
"surface": "builders-hall",
|
|
9182
9182
|
"text": "documented for later",
|
|
9183
9183
|
"file": "modules/hall-ui/public/government.js",
|
|
9184
|
-
"line":
|
|
9184
|
+
"line": 602,
|
|
9185
9185
|
"origin": "js-markup",
|
|
9186
9186
|
"confidence": "certain"
|
|
9187
9187
|
},
|
|
@@ -9496,7 +9496,7 @@
|
|
|
9496
9496
|
"surface": "builders-hall",
|
|
9497
9497
|
"text": "means expressible through the real configuration — a rank-based membership plus a pass rule — with no new code. This project has",
|
|
9498
9498
|
"file": "modules/hall-ui/public/government.js",
|
|
9499
|
-
"line":
|
|
9499
|
+
"line": 610,
|
|
9500
9500
|
"origin": "js-markup",
|
|
9501
9501
|
"confidence": "certain"
|
|
9502
9502
|
},
|
|
@@ -9622,7 +9622,7 @@
|
|
|
9622
9622
|
"surface": "builders-hall",
|
|
9623
9623
|
"text": "objected to",
|
|
9624
9624
|
"file": "modules/hall-ui/public/government.js",
|
|
9625
|
-
"line":
|
|
9625
|
+
"line": 694,
|
|
9626
9626
|
"origin": "js-markup",
|
|
9627
9627
|
"confidence": "certain"
|
|
9628
9628
|
},
|
|
@@ -9766,7 +9766,7 @@
|
|
|
9766
9766
|
"surface": "builders-hall",
|
|
9767
9767
|
"text": "recommended at your headcount",
|
|
9768
9768
|
"file": "modules/hall-ui/public/government.js",
|
|
9769
|
-
"line":
|
|
9769
|
+
"line": 603,
|
|
9770
9770
|
"origin": "js-markup",
|
|
9771
9771
|
"confidence": "certain"
|
|
9772
9772
|
},
|
|
@@ -9820,7 +9820,7 @@
|
|
|
9820
9820
|
"surface": "builders-hall",
|
|
9821
9821
|
"text": "selectable today",
|
|
9822
9822
|
"file": "modules/hall-ui/public/government.js",
|
|
9823
|
-
"line":
|
|
9823
|
+
"line": 601,
|
|
9824
9824
|
"origin": "js-markup",
|
|
9825
9825
|
"confidence": "certain"
|
|
9826
9826
|
},
|
|
@@ -9856,7 +9856,7 @@
|
|
|
9856
9856
|
"surface": "builders-hall",
|
|
9857
9857
|
"text": "tally: {…} yes · {…} no — sitting under",
|
|
9858
9858
|
"file": "modules/hall-ui/public/government.js",
|
|
9859
|
-
"line":
|
|
9859
|
+
"line": 786,
|
|
9860
9860
|
"origin": "js-markup",
|
|
9861
9861
|
"confidence": "certain"
|
|
9862
9862
|
},
|
|
@@ -10072,7 +10072,7 @@
|
|
|
10072
10072
|
"surface": "builders-hall",
|
|
10073
10073
|
"text": "your vote",
|
|
10074
10074
|
"file": "modules/hall-ui/public/government.js",
|
|
10075
|
-
"line":
|
|
10075
|
+
"line": 691,
|
|
10076
10076
|
"origin": "js-markup",
|
|
10077
10077
|
"confidence": "certain"
|
|
10078
10078
|
},
|
|
@@ -10567,7 +10567,7 @@
|
|
|
10567
10567
|
"surface": "builders-hall",
|
|
10568
10568
|
"text": "— {…}{…}",
|
|
10569
10569
|
"file": "modules/hall-ui/public/government.js",
|
|
10570
|
-
"line":
|
|
10570
|
+
"line": 605,
|
|
10571
10571
|
"origin": "js-markup",
|
|
10572
10572
|
"confidence": "certain"
|
|
10573
10573
|
},
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
-- core_221_claims_requested_worktree_name.sql
|
|
2
|
+
-- Task 1002709 (goal 1000024, build-pipeline integrity). A CORE migration, not a
|
|
3
|
+
-- lifecycle_ one, by the same rule core_204 states: it alters the shared
|
|
4
|
+
-- pre-module `claims` table and swaps an index on it, and a module migration
|
|
5
|
+
-- must stay additive within its own <key>_ tables (ADR 0083 §Decision #5).
|
|
6
|
+
-- Idempotent on re-run.
|
|
7
|
+
--
|
|
8
|
+
-- ── THE HOLE ────────────────────────────────────────────────────────────────
|
|
9
|
+
--
|
|
10
|
+
-- V3.R51 disambiguation (modules/lifecycle/db.js disambiguateWorktreeName)
|
|
11
|
+
-- silently RENAMES a requested worktree_name that collides with any other
|
|
12
|
+
-- currently-active claim: "overnight" is stored as "overnight-2". That rename is
|
|
13
|
+
-- correct and load-bearing — `.claude/worktrees/<name>` is a filesystem path and
|
|
14
|
+
-- two live builders pointing at one would stomp each other.
|
|
15
|
+
--
|
|
16
|
+
-- But both halves of the one-claim-per-tree rule then key on the STORED name,
|
|
17
|
+
-- while the CLI keeps sending the REQUESTED one (scripts/gds/claim.js reads the
|
|
18
|
+
-- real directory, which is never renamed). So:
|
|
19
|
+
--
|
|
20
|
+
-- 1. Builder X holds an active claim with worktree_name "overnight".
|
|
21
|
+
-- 2. Builder Y, session S, in a tree genuinely named "overnight", claims task
|
|
22
|
+
-- A. Y's name collides with X's, so it is stored as "overnight-2".
|
|
23
|
+
-- 3. Same session S, same tree, claims task B. The guard compares the incoming
|
|
24
|
+
-- "overnight" against the stored "overnight-2" — different tree, ALLOW. The
|
|
25
|
+
-- index sees (S,"overnight") vs (S,"overnight-2") — no collision, INSERT.
|
|
26
|
+
--
|
|
27
|
+
-- Result: ONE real working tree, ONE branch, TWO active claims — the exact
|
|
28
|
+
-- commingled-ship hazard task 1642 and ADR 0097 exist to prevent. Rare, because
|
|
29
|
+
-- worktree names are usually task-slug + random hex, but reachable for any fixed
|
|
30
|
+
-- reusable name: the overnight builder, box workflows, `--worktree` passed by
|
|
31
|
+
-- hand.
|
|
32
|
+
--
|
|
33
|
+
-- ── WHY A COLUMN AND NOT A CLEVERER GUARD ───────────────────────────────────
|
|
34
|
+
--
|
|
35
|
+
-- Matching "^<requested>-\d+$" in the guard would refuse a builder who genuinely
|
|
36
|
+
-- runs worktrees "foo" and "foo-2" side by side — reintroducing the false
|
|
37
|
+
-- refusal this whole family of bugs (1002570, 1002589, 1002522) is about. The
|
|
38
|
+
-- guard needs the name the claim was FILED under, which is simply not stored
|
|
39
|
+
-- today. So store it.
|
|
40
|
+
--
|
|
41
|
+
-- TWO NAMES, TWO JOBS, and keeping them separate is the point:
|
|
42
|
+
-- worktree_name the name on DISK after disambiguation. Still what
|
|
43
|
+
-- branch naming and the operator surfaces read, and
|
|
44
|
+
-- still globally unique across active claims.
|
|
45
|
+
-- requested_worktree_name the name the CALLER asked for. The identity of the
|
|
46
|
+
-- working TREE, which is what "do these two claims
|
|
47
|
+
-- share a tree" actually asks.
|
|
48
|
+
--
|
|
49
|
+
-- ── THE BACKFILL IS HONEST ABOUT WHAT IT CANNOT KNOW ────────────────────────
|
|
50
|
+
--
|
|
51
|
+
-- For historical rows the requested name was never recorded. Copying
|
|
52
|
+
-- worktree_name across is exactly right for every row that was NOT renamed (the
|
|
53
|
+
-- overwhelming majority — disambiguation only fires on a live collision) and is
|
|
54
|
+
-- the best available answer for the few that were. It cannot resurrect a lost
|
|
55
|
+
-- fact, and it does not pretend to: the column is nullable, and a row that was
|
|
56
|
+
-- renamed simply carries its post-rename name as its pre-rename one. No active
|
|
57
|
+
-- claim changes meaning, because the pair is only compared against other rows
|
|
58
|
+
-- backfilled the same way.
|
|
59
|
+
|
|
60
|
+
BEGIN;
|
|
61
|
+
|
|
62
|
+
ALTER TABLE claims
|
|
63
|
+
ADD COLUMN IF NOT EXISTS requested_worktree_name text;
|
|
64
|
+
|
|
65
|
+
COMMENT ON COLUMN claims.requested_worktree_name IS
|
|
66
|
+
'The worktree name the CALLER asked for, before V3.R51 disambiguation renamed it. The one-claim-per-working-tree guard keys on THIS (task 1002709); worktree_name remains the disambiguated on-disk name.';
|
|
67
|
+
|
|
68
|
+
-- Backfill (see the header): the requested name equals the stored name for every
|
|
69
|
+
-- row disambiguation never touched, and is the closest available answer for the
|
|
70
|
+
-- rest. Guarded so a re-run never overwrites a real value with a stale one.
|
|
71
|
+
UPDATE claims
|
|
72
|
+
SET requested_worktree_name = worktree_name
|
|
73
|
+
WHERE requested_worktree_name IS NULL;
|
|
74
|
+
|
|
75
|
+
-- ── heal before CREATE (the migration 174/180/204 shape) ────────────────────
|
|
76
|
+
--
|
|
77
|
+
-- A unique index refuses to create if live rows already violate it, which would
|
|
78
|
+
-- block the deploy. This heal keeps that safe for rows the NEW code writes.
|
|
79
|
+
--
|
|
80
|
+
-- WHAT IT CANNOT DO, STATED PLAINLY: it cannot find a commingled pair that
|
|
81
|
+
-- already exists. Consider the two shapes, after the backfill above:
|
|
82
|
+
--
|
|
83
|
+
-- commingled session S, stored "overnight" + "overnight-2", ONE real tree
|
|
84
|
+
-- legitimate session S, stored "foo" + "foo-2", TWO real trees
|
|
85
|
+
--
|
|
86
|
+
-- Both backfill to requested = their stored name, so both read as two distinct
|
|
87
|
+
-- trees, and the heal closes neither. They are INDISTINGUISHABLE in the data —
|
|
88
|
+
-- the requested name was never recorded, which is the entire bug. Any heuristic
|
|
89
|
+
-- that could close the first (stripping a "-\d+" suffix) would also close the
|
|
90
|
+
-- second, silently killing an active claim of a builder who did nothing wrong.
|
|
91
|
+
-- That is the same false-refusal trap that makes suffix-matching wrong in the
|
|
92
|
+
-- guard, and it is worse here because a migration acts without asking.
|
|
93
|
+
--
|
|
94
|
+
-- So this deliberately under-heals. The window it leaves is bounded and small:
|
|
95
|
+
-- any commingled pair already in flight stays in flight, and the FIRST of the
|
|
96
|
+
-- two to ship takes the tree's branch — the pre-existing hazard, unchanged. From
|
|
97
|
+
-- the moment the new guard code lands, no NEW pair can be created. Closing the
|
|
98
|
+
-- ledger honestly beats closing someone's claim on a guess.
|
|
99
|
+
--
|
|
100
|
+
-- The heal still earns its place: once rows carry a real requested name, two of
|
|
101
|
+
-- them CAN collide on it, and then this closes the later and keeps the earliest
|
|
102
|
+
-- — the same rule core_204 applied, and the one the guard would have applied had
|
|
103
|
+
-- it been able to see the collision at claim time.
|
|
104
|
+
WITH ranked AS (
|
|
105
|
+
SELECT c.id,
|
|
106
|
+
ROW_NUMBER() OVER (
|
|
107
|
+
PARTITION BY c.creator_session_id, COALESCE(c.requested_worktree_name, '')
|
|
108
|
+
ORDER BY c.claimed_at, c.id
|
|
109
|
+
) AS rn
|
|
110
|
+
FROM claims c
|
|
111
|
+
WHERE c.released_at IS NULL
|
|
112
|
+
AND c.creator_session_id IS NOT NULL
|
|
113
|
+
)
|
|
114
|
+
UPDATE claims c
|
|
115
|
+
SET released_at = now(),
|
|
116
|
+
outcome = COALESCE(c.outcome, 'abandoned')
|
|
117
|
+
FROM ranked r
|
|
118
|
+
WHERE c.id = r.id
|
|
119
|
+
AND r.rn > 1;
|
|
120
|
+
|
|
121
|
+
-- ── swap the index ──────────────────────────────────────────────────────────
|
|
122
|
+
-- COALESCE, not PG15's NULLS NOT DISTINCT, and it is load-bearing for the same
|
|
123
|
+
-- reason core_204 gives: in a plain multi-column unique index NULLs are
|
|
124
|
+
-- DISTINCT, so two worktree-less claims (one shared clone, whose single branch
|
|
125
|
+
-- genuinely commingles) would both insert and re-open the hole task 1642 closed.
|
|
126
|
+
--
|
|
127
|
+
-- The old index is DROPPED rather than left alongside. Keeping it would re-refuse
|
|
128
|
+
-- a legitimate second claim from a builder genuinely running "foo" and "foo-2"
|
|
129
|
+
-- in parallel — the false refusal this change exists to avoid reintroducing.
|
|
130
|
+
DROP INDEX IF EXISTS uniq_active_claim_per_session_worktree;
|
|
131
|
+
|
|
132
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uniq_active_claim_per_session_requested_worktree
|
|
133
|
+
ON claims (creator_session_id, (COALESCE(requested_worktree_name, '')))
|
|
134
|
+
WHERE released_at IS NULL AND creator_session_id IS NOT NULL;
|
|
135
|
+
|
|
136
|
+
-- (This index name and claimUniqueViolationError's SESSION_CLAIM_INDEXES set
|
|
137
|
+
-- must stay in lockstep — tests/one_active_claim_per_session.mjs pins both
|
|
138
|
+
-- sides, and all three generations stay in that set because a deploy applies
|
|
139
|
+
-- code and migration at different instants.)
|
|
140
|
+
|
|
141
|
+
INSERT INTO schema_migrations (version) VALUES ('core_221_claims_requested_worktree_name')
|
|
142
|
+
ON CONFLICT (version) DO NOTHING;
|
|
143
|
+
|
|
144
|
+
COMMIT;
|
|
@@ -664,7 +664,12 @@ async function boardRoomView({ closedLimit = 20 } = {}) {
|
|
|
664
664
|
function recommendedCharter(headcount) {
|
|
665
665
|
const n = Number(headcount) || 0;
|
|
666
666
|
if (n <= 2) return { key: 'monarchy', rung: 'solo/pair', why: 'informal coordination has nothing to break yet — the founding monarchy is the honest shape' };
|
|
667
|
-
|
|
667
|
+
// "all trusted members" was not expressible under the pre-1003094 exact-match
|
|
668
|
+
// grammar, so at this headcount the library recommended a charter it could not
|
|
669
|
+
// state (task 1003095). It is expressible now — `rank:xenos+` seats every
|
|
670
|
+
// builder — and the wording says the thing the predicate actually does rather
|
|
671
|
+
// than a phrase with no predicate behind it.
|
|
672
|
+
if (n <= 8) return { key: 'direct-democracy', rung: 'circle (3–8)', why: 'a consent circle of every builder (rank:xenos+) — the rung this ladder was written on' };
|
|
668
673
|
if (n <= 49) return { key: 'presidential', rung: '~9: name an executive', why: 'past ~9, naming an executive beats everyone coordinating with everyone' };
|
|
669
674
|
if (n <= 149) return { key: 'foundation', rung: '~50: written charter + council', why: 'past ~50, the charter must be written down and a council carries it' };
|
|
670
675
|
return { key: 'federation', rung: '~150: adjudication separates — federate beyond', why: 'past ~150, one body cannot both govern and adjudicate; federate' };
|
|
@@ -539,15 +539,40 @@
|
|
|
539
539
|
// config — a rank-predicate membership plus first_ratifier|consent — so nobody
|
|
540
540
|
// is misled about what a settings change can and cannot reach. Source: the
|
|
541
541
|
// Six-Variables synthesis (Verro Labs, Aug 2026), Parts III–IV, via the spec.
|
|
542
|
+
// EVERY SELECTABLE CHARTER CARRIES A MACHINE-CHECKABLE PREDICATE (task 1003095).
|
|
543
|
+
//
|
|
544
|
+
// This library is the ONLY place in the product that tells an owner what to
|
|
545
|
+
// type. That makes a wrong `how` line worse than no library at all — and two
|
|
546
|
+
// of them were wrong. Under the pre-1003094 exact-match grammar,
|
|
547
|
+
// `membership rank:xenos (everyone)` seated NEWCOMERS ONLY: an owner who
|
|
548
|
+
// followed this page handed the board to the least-trusted tier and locked
|
|
549
|
+
// themselves out. Timocratic Republic was true as far as it went and silent
|
|
550
|
+
// about the same surprise.
|
|
551
|
+
//
|
|
552
|
+
// So the claim is no longer prose. `predicate` is the exact string an owner
|
|
553
|
+
// types, and `seatsRanks` is what it must resolve to — checked in
|
|
554
|
+
// tests/government_charter_library.mjs against the REAL parser
|
|
555
|
+
// (modules/government/membership-predicate.js), not against a copy of the
|
|
556
|
+
// intent. If the grammar ever changes underneath this file again, that test
|
|
557
|
+
// reds instead of the page quietly lying.
|
|
558
|
+
//
|
|
559
|
+
// `predicate: null` means the predicate is the owner's to choose (Foundation
|
|
560
|
+
// names their own custom rank), which is honest rather than unchecked — the
|
|
561
|
+
// test asserts such a charter says so in its `how`.
|
|
542
562
|
const CHARTERS = [
|
|
543
563
|
{ key: 'monarchy', name: 'Monarchy / BDFL', selectable: true,
|
|
544
|
-
|
|
564
|
+
predicate: 'rank:archon', seatsRanks: ['archon'],
|
|
565
|
+
how: 'membership rank:archon · first ratifier · no clock — the shipped default',
|
|
545
566
|
blurb: 'One trusted executive decides. Fast, legible, and honest at founding scale; the shape every project actually starts in.' },
|
|
546
567
|
{ key: 'timocratic-republic', name: 'Timocratic Republic', selectable: true,
|
|
547
|
-
|
|
568
|
+
predicate: 'rank:metic+', seatsRanks: ['metic', 'archon'],
|
|
569
|
+
how: 'membership rank:metic+ (metic and above — Archons included) · consent · a timed sitting',
|
|
548
570
|
blurb: 'Standing accrues to those actively building: the franchise follows earned rank, not headcount.' },
|
|
549
571
|
{ key: 'direct-democracy', name: 'Direct Democracy', selectable: true,
|
|
550
|
-
|
|
572
|
+
predicate: 'rank:xenos+', seatsRanks: ['xenos', 'thetes', 'metic', 'archon'],
|
|
573
|
+
// The `+` is load-bearing and is why it is spelled out: a bare rank:xenos
|
|
574
|
+
// seats newcomers ONLY, which is what this line used to say.
|
|
575
|
+
how: 'membership rank:xenos+ (every builder — the + is what makes it everyone) · consent · a timed sitting',
|
|
551
576
|
blurb: 'Every builder sits on the board. Consent keeps it unblockable: silence passes, one reasoned objection returns.' },
|
|
552
577
|
{ key: 'presidential', name: 'Presidential', selectable: false,
|
|
553
578
|
blurb: 'An ELECTED executive with a fixed term. Needs election machinery this system does not have yet — documented for later.' },
|
|
@@ -556,7 +581,12 @@
|
|
|
556
581
|
{ key: 'federation', name: 'Federation', selectable: false,
|
|
557
582
|
blurb: 'Multiple chambers or sub-projects, each self-governing, coordinating through a compact. The >150 shape — documented for later.' },
|
|
558
583
|
{ key: 'foundation', name: 'Foundation', selectable: true,
|
|
559
|
-
|
|
584
|
+
// The one selectable charter whose predicate the OWNER names — a custom
|
|
585
|
+
// rank is a DB row this page cannot know. Deliberately null rather than a
|
|
586
|
+
// made-up example, and the `how` says so: `+` does not apply to a custom
|
|
587
|
+
// rank (it has no ladder position), so a bare key is the correct form here.
|
|
588
|
+
predicate: null, seatsRanks: null,
|
|
589
|
+
how: 'membership rank:<your-trustee-rank> — a CUSTOM rank you create on the Ranks tab, named exactly (no +, a custom rank has no ladder position) · consent · a timed sitting',
|
|
560
590
|
blurb: 'A written charter carried by a small appointed council. Expressible today by governing through a custom rank.' },
|
|
561
591
|
];
|
|
562
592
|
const TRAJECTORIES = [
|
package/modules/lifecycle/db.js
CHANGED
|
@@ -2600,7 +2600,13 @@ async function unsatisfiedDeps(taskId, client = pool) {
|
|
|
2600
2600
|
// carry the old index.
|
|
2601
2601
|
const SESSION_CLAIM_INDEXES = new Set([
|
|
2602
2602
|
'uniq_active_claim_per_session', // migration 180 — session-only (pre-re-key)
|
|
2603
|
-
'uniq_active_claim_per_session_worktree', // core_204 (task 1002570) — per (session, worktree)
|
|
2603
|
+
'uniq_active_claim_per_session_worktree', // core_204 (task 1002570) — per (session, stored worktree)
|
|
2604
|
+
// core_221 (task 1002709) — per (session, REQUESTED worktree). All three
|
|
2605
|
+
// generations stay listed: a deploy applies code and migration at different
|
|
2606
|
+
// instants, so an instance may still carry an older index, and an unrecognised
|
|
2607
|
+
// constraint falls closed onto the generic ALREADY_CLAIMED — which for this
|
|
2608
|
+
// case is the wrong, phantom-rival answer.
|
|
2609
|
+
'uniq_active_claim_per_session_requested_worktree',
|
|
2604
2610
|
]);
|
|
2605
2611
|
// Branch on err.constraint; an UNKNOWN constraint fails CLOSED on the generic
|
|
2606
2612
|
// ALREADY_CLAIMED (never widen a claim refusal). Pure + exported for DB-free
|
|
@@ -2830,7 +2836,18 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
|
|
|
2830
2836
|
// task 1002522: fold ''/whitespace → NULL before anything keys on the name.
|
|
2831
2837
|
// core_204's index folds them together anyway (COALESCE(worktree_name, ''));
|
|
2832
2838
|
// normalizing here means the stored row matches what the guard compared.
|
|
2833
|
-
|
|
2839
|
+
// task 1002709: BOTH names are stored, and they answer different questions.
|
|
2840
|
+
// `requestedWorktreeName` is what the caller asked for — the identity of the
|
|
2841
|
+
// working TREE, which is what "do these two claims share a tree" actually
|
|
2842
|
+
// asks. `finalWorktreeName` is what lands on disk after disambiguation, which
|
|
2843
|
+
// is what must stay globally unique across active claims.
|
|
2844
|
+
//
|
|
2845
|
+
// Keying the one-claim-per-tree guard on the STORED name was the hole: a
|
|
2846
|
+
// second claim from the same session in the same tree compared "overnight"
|
|
2847
|
+
// against a previously-renamed "overnight-2", saw a different tree, and let
|
|
2848
|
+
// a commingling claim through. See core_221 for the full repro.
|
|
2849
|
+
const requestedWorktreeName = foldWorktreeName(worktreeName);
|
|
2850
|
+
let finalWorktreeName = requestedWorktreeName;
|
|
2834
2851
|
if (finalWorktreeName) {
|
|
2835
2852
|
const { rows: activeNameRows } = await client.query(
|
|
2836
2853
|
`SELECT worktree_name FROM claims
|
|
@@ -2841,10 +2858,10 @@ async function claimTask({ taskId, builderId, worktreeName, headSha, creatorSess
|
|
|
2841
2858
|
}
|
|
2842
2859
|
|
|
2843
2860
|
const insertClaim = await client.query(
|
|
2844
|
-
`INSERT INTO claims (task_id, builder_id, worktree_name, head_sha, reserved_migration_number, creator_session_id)
|
|
2845
|
-
VALUES ($1, $2, $3, $4, $5, $6)
|
|
2846
|
-
RETURNING id, task_id, builder_id, worktree_name, claimed_at, head_sha, reserved_migration_number`,
|
|
2847
|
-
[taskId, builderId, finalWorktreeName, headSha ?? null, reservedMigrationNumber, creatorSessionId ?? null]
|
|
2861
|
+
`INSERT INTO claims (task_id, builder_id, worktree_name, requested_worktree_name, head_sha, reserved_migration_number, creator_session_id)
|
|
2862
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
2863
|
+
RETURNING id, task_id, builder_id, worktree_name, requested_worktree_name, claimed_at, head_sha, reserved_migration_number`,
|
|
2864
|
+
[taskId, builderId, finalWorktreeName, requestedWorktreeName, headSha ?? null, reservedMigrationNumber, creatorSessionId ?? null]
|
|
2848
2865
|
);
|
|
2849
2866
|
|
|
2850
2867
|
await client.query(`UPDATE tasks SET status = 'active' WHERE id = $1`, [taskId]);
|
|
@@ -3198,10 +3215,16 @@ async function claimTasksBatch({ taskIds, builderId, worktreePrefix, creatorSess
|
|
|
3198
3215
|
: null;
|
|
3199
3216
|
const rowCreatorSessionId = i === 0 ? (creatorSessionId ?? null) : null;
|
|
3200
3217
|
try {
|
|
3218
|
+
// task 1002709: requested == final on this path by construction — each
|
|
3219
|
+
// row gets its own `<prefix>-<taskId>` tree and nothing disambiguates
|
|
3220
|
+
// them. It is still written explicitly rather than left NULL: core_221's
|
|
3221
|
+
// index keys on COALESCE(requested_worktree_name, ''), so a NULL here
|
|
3222
|
+
// would make a row that HAS a worktree read as worktree-less and collide
|
|
3223
|
+
// with the shared-clone group.
|
|
3201
3224
|
const insertClaim = await client.query(
|
|
3202
|
-
`INSERT INTO claims (task_id, builder_id, worktree_name, head_sha, reserved_migration_number, creator_session_id)
|
|
3203
|
-
VALUES ($1, $2, $3, NULL, $4, $5)
|
|
3204
|
-
RETURNING id, task_id, builder_id, worktree_name, claimed_at, head_sha, reserved_migration_number`,
|
|
3225
|
+
`INSERT INTO claims (task_id, builder_id, worktree_name, requested_worktree_name, head_sha, reserved_migration_number, creator_session_id)
|
|
3226
|
+
VALUES ($1, $2, $3, $3, NULL, $4, $5)
|
|
3227
|
+
RETURNING id, task_id, builder_id, worktree_name, requested_worktree_name, claimed_at, head_sha, reserved_migration_number`,
|
|
3205
3228
|
[t.id, builderId, worktreeName, reservedMigrationNumber, rowCreatorSessionId]
|
|
3206
3229
|
);
|
|
3207
3230
|
inserted.push(insertClaim.rows[0]);
|
|
@@ -4843,7 +4866,12 @@ async function activeClaimCountForBuilder(builderId, deps = {}) {
|
|
|
4843
4866
|
async function getActiveClaimsForSession(creatorSessionId) {
|
|
4844
4867
|
if (!creatorSessionId) return [];
|
|
4845
4868
|
const { rows } = await pool.query(
|
|
4846
|
-
|
|
4869
|
+
// task 1002709: requested_worktree_name rides along because the guard keys on
|
|
4870
|
+
// it — comparing the incoming REQUESTED name against a stored DISAMBIGUATED
|
|
4871
|
+
// one is what let a second same-tree claim through. worktree_name stays
|
|
4872
|
+
// selected: callers still render the on-disk name.
|
|
4873
|
+
`SELECT c.id AS claim_id, c.task_id, c.creator_session_id, c.worktree_name,
|
|
4874
|
+
c.requested_worktree_name, c.claimed_at,
|
|
4847
4875
|
t.title
|
|
4848
4876
|
FROM claims c JOIN tasks t ON t.id = c.task_id
|
|
4849
4877
|
WHERE c.creator_session_id = $1 AND c.released_at IS NULL
|