@bongos/core 1.19.272 → 1.19.274
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 +32 -17
- package/docs/copy-inventory.md +37 -37
- package/docs/copy-registry.json +43 -43
- package/modules/hall-ui/public/dom-utils.js +40 -0
- package/modules/hall-ui/public/task.js +9 -3
- package/modules/hall-ui/public/work.js +16 -7
- package/modules/lifecycle/est-advisory.js +162 -0
- package/modules/lifecycle/routes/tasks.js +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +5 -1
- package/tests/est_advisory.mjs +183 -0
- package/tests/goal_advisory.mjs +6 -1
- package/tests/hall_error_envelope.mjs +264 -0
package/docs/copy-registry.json
CHANGED
|
@@ -101,8 +101,8 @@
|
|
|
101
101
|
"at": [
|
|
102
102
|
"modules/hall-ui/public/goals-page.js:674",
|
|
103
103
|
"modules/hall-ui/public/goals-page.js:926",
|
|
104
|
-
"modules/hall-ui/public/work.js:
|
|
105
|
-
"modules/hall-ui/public/work.js:
|
|
104
|
+
"modules/hall-ui/public/work.js:751",
|
|
105
|
+
"modules/hall-ui/public/work.js:821"
|
|
106
106
|
]
|
|
107
107
|
},
|
|
108
108
|
{
|
|
@@ -134,8 +134,8 @@
|
|
|
134
134
|
"text": "Copy prompt",
|
|
135
135
|
"occurrences": 3,
|
|
136
136
|
"at": [
|
|
137
|
-
"modules/hall-ui/public/task.js:
|
|
138
|
-
"modules/hall-ui/public/task.js:
|
|
137
|
+
"modules/hall-ui/public/task.js:258",
|
|
138
|
+
"modules/hall-ui/public/task.js:275",
|
|
139
139
|
"modules/hall-ui/public/work.js:446"
|
|
140
140
|
]
|
|
141
141
|
},
|
|
@@ -179,8 +179,8 @@
|
|
|
179
179
|
"occurrences": 3,
|
|
180
180
|
"at": [
|
|
181
181
|
"modules/hall-ui/public/goals-page.js:949",
|
|
182
|
-
"modules/hall-ui/public/work.js:
|
|
183
|
-
"modules/hall-ui/public/work.js:
|
|
182
|
+
"modules/hall-ui/public/work.js:792",
|
|
183
|
+
"modules/hall-ui/public/work.js:845"
|
|
184
184
|
]
|
|
185
185
|
},
|
|
186
186
|
{
|
|
@@ -385,7 +385,7 @@
|
|
|
385
385
|
"occurrences": 2,
|
|
386
386
|
"at": [
|
|
387
387
|
"modules/hall-ui/public/goals-page.js:677",
|
|
388
|
-
"modules/hall-ui/public/work.js:
|
|
388
|
+
"modules/hall-ui/public/work.js:754"
|
|
389
389
|
]
|
|
390
390
|
},
|
|
391
391
|
{
|
|
@@ -775,7 +775,7 @@
|
|
|
775
775
|
"occurrences": 2,
|
|
776
776
|
"at": [
|
|
777
777
|
"modules/hall-ui/public/profile.js:144",
|
|
778
|
-
"modules/hall-ui/public/work.js:
|
|
778
|
+
"modules/hall-ui/public/work.js:914"
|
|
779
779
|
]
|
|
780
780
|
},
|
|
781
781
|
{
|
|
@@ -795,7 +795,7 @@
|
|
|
795
795
|
"occurrences": 2,
|
|
796
796
|
"at": [
|
|
797
797
|
"modules/hall-ui/public/goals-page.js:702",
|
|
798
|
-
"modules/hall-ui/public/work.js:
|
|
798
|
+
"modules/hall-ui/public/work.js:963"
|
|
799
799
|
]
|
|
800
800
|
},
|
|
801
801
|
{
|
|
@@ -915,7 +915,7 @@
|
|
|
915
915
|
"occurrences": 2,
|
|
916
916
|
"at": [
|
|
917
917
|
"modules/hall-ui/public/goals-page.js:944",
|
|
918
|
-
"modules/hall-ui/public/work.js:
|
|
918
|
+
"modules/hall-ui/public/work.js:837"
|
|
919
919
|
]
|
|
920
920
|
},
|
|
921
921
|
{
|
|
@@ -1519,7 +1519,7 @@
|
|
|
1519
1519
|
"surface": "builders-hall",
|
|
1520
1520
|
"text": ", follow its acceptance criteria, stay within touches[], /builder-ship",
|
|
1521
1521
|
"file": "modules/hall-ui/public/work.js",
|
|
1522
|
-
"line":
|
|
1522
|
+
"line": 739,
|
|
1523
1523
|
"origin": "js-markup",
|
|
1524
1524
|
"confidence": "certain"
|
|
1525
1525
|
},
|
|
@@ -2032,7 +2032,7 @@
|
|
|
2032
2032
|
"surface": "builders-hall",
|
|
2033
2033
|
"text": "Awaiting the grader / a confirmation before it can ship.",
|
|
2034
2034
|
"file": "modules/hall-ui/public/task.js",
|
|
2035
|
-
"line":
|
|
2035
|
+
"line": 253,
|
|
2036
2036
|
"origin": "js-markup",
|
|
2037
2037
|
"confidence": "certain"
|
|
2038
2038
|
},
|
|
@@ -2311,7 +2311,7 @@
|
|
|
2311
2311
|
"surface": "builders-hall",
|
|
2312
2312
|
"text": "Checking dependency order…",
|
|
2313
2313
|
"file": "modules/hall-ui/public/work.js",
|
|
2314
|
-
"line":
|
|
2314
|
+
"line": 700,
|
|
2315
2315
|
"origin": "js-text-assign",
|
|
2316
2316
|
"confidence": "certain"
|
|
2317
2317
|
},
|
|
@@ -2320,7 +2320,7 @@
|
|
|
2320
2320
|
"surface": "builders-hall",
|
|
2321
2321
|
"text": "Checking for conflicts…",
|
|
2322
2322
|
"file": "modules/hall-ui/public/work.js",
|
|
2323
|
-
"line":
|
|
2323
|
+
"line": 679,
|
|
2324
2324
|
"origin": "js-text-assign",
|
|
2325
2325
|
"confidence": "certain"
|
|
2326
2326
|
},
|
|
@@ -2401,7 +2401,7 @@
|
|
|
2401
2401
|
"surface": "builders-hall",
|
|
2402
2402
|
"text": "Claimed {…} tasks. Open a terminal to start work.",
|
|
2403
2403
|
"file": "modules/hall-ui/public/work.js",
|
|
2404
|
-
"line":
|
|
2404
|
+
"line": 754,
|
|
2405
2405
|
"origin": "js-notice",
|
|
2406
2406
|
"confidence": "certain"
|
|
2407
2407
|
},
|
|
@@ -2608,7 +2608,7 @@
|
|
|
2608
2608
|
"surface": "builders-hall",
|
|
2609
2609
|
"text": "Confirm that verification for #{…} actually passed?",
|
|
2610
2610
|
"file": "modules/hall-ui/public/task.js",
|
|
2611
|
-
"line":
|
|
2611
|
+
"line": 290,
|
|
2612
2612
|
"origin": "js-notice",
|
|
2613
2613
|
"confidence": "certain"
|
|
2614
2614
|
},
|
|
@@ -2743,7 +2743,7 @@
|
|
|
2743
2743
|
"surface": "builders-hall",
|
|
2744
2744
|
"text": "Copy prompt",
|
|
2745
2745
|
"file": "modules/hall-ui/public/task.js",
|
|
2746
|
-
"line":
|
|
2746
|
+
"line": 258,
|
|
2747
2747
|
"origin": "js-markup",
|
|
2748
2748
|
"confidence": "certain"
|
|
2749
2749
|
},
|
|
@@ -2752,7 +2752,7 @@
|
|
|
2752
2752
|
"surface": "builders-hall",
|
|
2753
2753
|
"text": "Copy prompt",
|
|
2754
2754
|
"file": "modules/hall-ui/public/task.js",
|
|
2755
|
-
"line":
|
|
2755
|
+
"line": 275,
|
|
2756
2756
|
"origin": "js-text-assign",
|
|
2757
2757
|
"confidence": "certain"
|
|
2758
2758
|
},
|
|
@@ -2950,7 +2950,7 @@
|
|
|
2950
2950
|
"surface": "builders-hall",
|
|
2951
2951
|
"text": "Could not load #{…}",
|
|
2952
2952
|
"file": "modules/hall-ui/public/task.js",
|
|
2953
|
-
"line":
|
|
2953
|
+
"line": 309,
|
|
2954
2954
|
"origin": "js-markup",
|
|
2955
2955
|
"confidence": "certain"
|
|
2956
2956
|
},
|
|
@@ -4147,7 +4147,7 @@
|
|
|
4147
4147
|
"surface": "builders-hall",
|
|
4148
4148
|
"text": "Goal #{…}",
|
|
4149
4149
|
"file": "modules/hall-ui/public/task.js",
|
|
4150
|
-
"line":
|
|
4150
|
+
"line": 202,
|
|
4151
4151
|
"origin": "js-markup",
|
|
4152
4152
|
"confidence": "certain"
|
|
4153
4153
|
},
|
|
@@ -4723,7 +4723,7 @@
|
|
|
4723
4723
|
"surface": "builders-hall",
|
|
4724
4724
|
"text": "Last claimed by",
|
|
4725
4725
|
"file": "modules/hall-ui/public/task.js",
|
|
4726
|
-
"line":
|
|
4726
|
+
"line": 213,
|
|
4727
4727
|
"origin": "js-markup",
|
|
4728
4728
|
"confidence": "certain"
|
|
4729
4729
|
},
|
|
@@ -5560,7 +5560,7 @@
|
|
|
5560
5560
|
"surface": "builders-hall",
|
|
5561
5561
|
"text": "Not found",
|
|
5562
5562
|
"file": "modules/hall-ui/public/task.js",
|
|
5563
|
-
"line":
|
|
5563
|
+
"line": 304,
|
|
5564
5564
|
"origin": "js-text-assign",
|
|
5565
5565
|
"confidence": "certain"
|
|
5566
5566
|
},
|
|
@@ -5632,7 +5632,7 @@
|
|
|
5632
5632
|
"surface": "builders-hall",
|
|
5633
5633
|
"text": "Nothing in the backlog right now.",
|
|
5634
5634
|
"file": "modules/hall-ui/public/work.js",
|
|
5635
|
-
"line":
|
|
5635
|
+
"line": 953,
|
|
5636
5636
|
"origin": "js-markup",
|
|
5637
5637
|
"confidence": "certain"
|
|
5638
5638
|
},
|
|
@@ -5659,7 +5659,7 @@
|
|
|
5659
5659
|
"surface": "builders-hall",
|
|
5660
5660
|
"text": "Nothing shipped yet.",
|
|
5661
5661
|
"file": "modules/hall-ui/public/work.js",
|
|
5662
|
-
"line":
|
|
5662
|
+
"line": 914,
|
|
5663
5663
|
"origin": "js-markup",
|
|
5664
5664
|
"confidence": "certain"
|
|
5665
5665
|
},
|
|
@@ -5794,7 +5794,7 @@
|
|
|
5794
5794
|
"surface": "builders-hall",
|
|
5795
5795
|
"text": "Open task #{…}",
|
|
5796
5796
|
"file": "modules/hall-ui/public/work.js",
|
|
5797
|
-
"line":
|
|
5797
|
+
"line": 963,
|
|
5798
5798
|
"origin": "js-markup",
|
|
5799
5799
|
"confidence": "certain"
|
|
5800
5800
|
},
|
|
@@ -5839,7 +5839,7 @@
|
|
|
5839
5839
|
"surface": "builders-hall",
|
|
5840
5840
|
"text": "Open the Work Board",
|
|
5841
5841
|
"file": "modules/hall-ui/public/task.js",
|
|
5842
|
-
"line":
|
|
5842
|
+
"line": 258,
|
|
5843
5843
|
"origin": "js-markup",
|
|
5844
5844
|
"confidence": "certain"
|
|
5845
5845
|
},
|
|
@@ -6352,7 +6352,7 @@
|
|
|
6352
6352
|
"surface": "builders-hall",
|
|
6353
6353
|
"text": "Recent history could not be loaded right now.",
|
|
6354
6354
|
"file": "modules/hall-ui/public/work.js",
|
|
6355
|
-
"line":
|
|
6355
|
+
"line": 1052,
|
|
6356
6356
|
"origin": "js-markup",
|
|
6357
6357
|
"confidence": "certain"
|
|
6358
6358
|
},
|
|
@@ -6433,7 +6433,7 @@
|
|
|
6433
6433
|
"surface": "builders-hall",
|
|
6434
6434
|
"text": "Release your claim on #{…}? It returns to the queue and no {…} are earned.",
|
|
6435
6435
|
"file": "modules/hall-ui/public/work.js",
|
|
6436
|
-
"line":
|
|
6436
|
+
"line": 837,
|
|
6437
6437
|
"origin": "js-notice",
|
|
6438
6438
|
"confidence": "certain"
|
|
6439
6439
|
},
|
|
@@ -6442,7 +6442,7 @@
|
|
|
6442
6442
|
"surface": "builders-hall",
|
|
6443
6443
|
"text": "Release {…}? {…} to the queue and no {…} are earned.",
|
|
6444
6444
|
"file": "modules/hall-ui/public/work.js",
|
|
6445
|
-
"line":
|
|
6445
|
+
"line": 786,
|
|
6446
6446
|
"origin": "js-notice",
|
|
6447
6447
|
"confidence": "certain"
|
|
6448
6448
|
},
|
|
@@ -6460,7 +6460,7 @@
|
|
|
6460
6460
|
"surface": "builders-hall",
|
|
6461
6461
|
"text": "Released #{…}. It returns to the queue.",
|
|
6462
6462
|
"file": "modules/hall-ui/public/work.js",
|
|
6463
|
-
"line":
|
|
6463
|
+
"line": 792,
|
|
6464
6464
|
"origin": "js-notice",
|
|
6465
6465
|
"confidence": "certain"
|
|
6466
6466
|
},
|
|
@@ -6469,7 +6469,7 @@
|
|
|
6469
6469
|
"surface": "builders-hall",
|
|
6470
6470
|
"text": "Released #{…}. It returns to the queue.",
|
|
6471
6471
|
"file": "modules/hall-ui/public/work.js",
|
|
6472
|
-
"line":
|
|
6472
|
+
"line": 845,
|
|
6473
6473
|
"origin": "js-notice",
|
|
6474
6474
|
"confidence": "certain"
|
|
6475
6475
|
},
|
|
@@ -6937,7 +6937,7 @@
|
|
|
6937
6937
|
"surface": "builders-hall",
|
|
6938
6938
|
"text": "Select at least 2 waiting tasks to copy a parallel-session prompt.",
|
|
6939
6939
|
"file": "modules/hall-ui/public/work.js",
|
|
6940
|
-
"line":
|
|
6940
|
+
"line": 664,
|
|
6941
6941
|
"origin": "js-text-assign",
|
|
6942
6942
|
"confidence": "certain"
|
|
6943
6943
|
},
|
|
@@ -6946,7 +6946,7 @@
|
|
|
6946
6946
|
"surface": "builders-hall",
|
|
6947
6947
|
"text": "Select at least 2 waiting tasks to copy a sequential prompt.",
|
|
6948
6948
|
"file": "modules/hall-ui/public/work.js",
|
|
6949
|
-
"line":
|
|
6949
|
+
"line": 689,
|
|
6950
6950
|
"origin": "js-text-assign",
|
|
6951
6951
|
"confidence": "certain"
|
|
6952
6952
|
},
|
|
@@ -7090,7 +7090,7 @@
|
|
|
7090
7090
|
"surface": "builders-hall",
|
|
7091
7091
|
"text": "Ship it",
|
|
7092
7092
|
"file": "modules/hall-ui/public/task.js",
|
|
7093
|
-
"line":
|
|
7093
|
+
"line": 249,
|
|
7094
7094
|
"origin": "js-markup",
|
|
7095
7095
|
"confidence": "certain"
|
|
7096
7096
|
},
|
|
@@ -7099,7 +7099,7 @@
|
|
|
7099
7099
|
"surface": "builders-hall",
|
|
7100
7100
|
"text": "Shipped {…}{…}.",
|
|
7101
7101
|
"file": "modules/hall-ui/public/task.js",
|
|
7102
|
-
"line":
|
|
7102
|
+
"line": 234,
|
|
7103
7103
|
"origin": "js-markup",
|
|
7104
7104
|
"confidence": "certain"
|
|
7105
7105
|
},
|
|
@@ -7378,7 +7378,7 @@
|
|
|
7378
7378
|
"surface": "builders-hall",
|
|
7379
7379
|
"text": "Task #{…}",
|
|
7380
7380
|
"file": "modules/hall-ui/public/task.js",
|
|
7381
|
-
"line":
|
|
7381
|
+
"line": 176,
|
|
7382
7382
|
"origin": "js-text-assign",
|
|
7383
7383
|
"confidence": "certain"
|
|
7384
7384
|
},
|
|
@@ -7441,7 +7441,7 @@
|
|
|
7441
7441
|
"surface": "builders-hall",
|
|
7442
7442
|
"text": "Tasks are numbered separately from GitHub pull requests, so this may be a PR rather than a GDS task.",
|
|
7443
7443
|
"file": "modules/hall-ui/public/task.js",
|
|
7444
|
-
"line":
|
|
7444
|
+
"line": 299,
|
|
7445
7445
|
"origin": "js-markup",
|
|
7446
7446
|
"confidence": "certain"
|
|
7447
7447
|
},
|
|
@@ -7810,7 +7810,7 @@
|
|
|
7810
7810
|
"surface": "builders-hall",
|
|
7811
7811
|
"text": "The task list could not be loaded right now.",
|
|
7812
7812
|
"file": "modules/hall-ui/public/work.js",
|
|
7813
|
-
"line":
|
|
7813
|
+
"line": 1106,
|
|
7814
7814
|
"origin": "js-markup",
|
|
7815
7815
|
"confidence": "certain"
|
|
7816
7816
|
},
|
|
@@ -7828,7 +7828,7 @@
|
|
|
7828
7828
|
"surface": "builders-hall",
|
|
7829
7829
|
"text": "The work board failed to load. Reload to try again.",
|
|
7830
7830
|
"file": "modules/hall-ui/public/work.js",
|
|
7831
|
-
"line":
|
|
7831
|
+
"line": 1275,
|
|
7832
7832
|
"origin": "js-notice",
|
|
7833
7833
|
"confidence": "certain"
|
|
7834
7834
|
},
|
|
@@ -8089,7 +8089,7 @@
|
|
|
8089
8089
|
"surface": "builders-hall",
|
|
8090
8090
|
"text": "Try reloading in a moment.",
|
|
8091
8091
|
"file": "modules/hall-ui/public/task.js",
|
|
8092
|
-
"line":
|
|
8092
|
+
"line": 309,
|
|
8093
8093
|
"origin": "js-markup",
|
|
8094
8094
|
"confidence": "certain"
|
|
8095
8095
|
},
|
|
@@ -8269,7 +8269,7 @@
|
|
|
8269
8269
|
"surface": "builders-hall",
|
|
8270
8270
|
"text": "Welcome, {…}. Claim and release tasks and follow work in progress — no terminal needed.",
|
|
8271
8271
|
"file": "modules/hall-ui/public/work.js",
|
|
8272
|
-
"line":
|
|
8272
|
+
"line": 1043,
|
|
8273
8273
|
"origin": "js-text-assign",
|
|
8274
8274
|
"confidence": "certain"
|
|
8275
8275
|
},
|
|
@@ -8566,7 +8566,7 @@
|
|
|
8566
8566
|
"surface": "builders-hall",
|
|
8567
8567
|
"text": "You claimed #{…}. Open a terminal to start work.",
|
|
8568
8568
|
"file": "modules/hall-ui/public/work.js",
|
|
8569
|
-
"line":
|
|
8569
|
+
"line": 751,
|
|
8570
8570
|
"origin": "js-notice",
|
|
8571
8571
|
"confidence": "certain"
|
|
8572
8572
|
},
|
|
@@ -8575,7 +8575,7 @@
|
|
|
8575
8575
|
"surface": "builders-hall",
|
|
8576
8576
|
"text": "You claimed #{…}. Open a terminal to start work.",
|
|
8577
8577
|
"file": "modules/hall-ui/public/work.js",
|
|
8578
|
-
"line":
|
|
8578
|
+
"line": 821,
|
|
8579
8579
|
"origin": "js-notice",
|
|
8580
8580
|
"confidence": "certain"
|
|
8581
8581
|
},
|
|
@@ -230,8 +230,48 @@
|
|
|
230
230
|
return { label: label || stripMd(raw).slice(0, 80), detail };
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
|
|
234
|
+
// ---- ADR 0116 error envelope (task 1002462) -------------------------------
|
|
235
|
+
//
|
|
236
|
+
// The server returns `{ error: { code, message, details } }` via res.fail().
|
|
237
|
+
// The code is NESTED, so a bare `data.error === 'CODE'` test compares a string
|
|
238
|
+
// to an OBJECT and silently never matches: every switch falls through to its
|
|
239
|
+
// generic default. That degrades into a wrong-but-plausible toast rather than a
|
|
240
|
+
// visible break, so no smoke test catches it — which is how the same defect
|
|
241
|
+
// survived independently across four hall surfaces (work.js, task.js, and
|
|
242
|
+
// hand-rolled copies in goals-page.js and task-detail.js).
|
|
243
|
+
//
|
|
244
|
+
// These live here, in the shared toolkit every hall page already loads first,
|
|
245
|
+
// so a fifth copy is never the easiest option. They take the RESPONSE BODY —
|
|
246
|
+
// callers pass `err.data` — and each accepts BOTH the envelope and the
|
|
247
|
+
// pre-envelope flat shape `{ error: 'CODE', ...fields }`, so an older server
|
|
248
|
+
// still maps. Pure: no DOM, no network, no page-specific behaviour (unlike the
|
|
249
|
+
// network helpers, which are deliberately NOT in this file).
|
|
250
|
+
function errCode(data) {
|
|
251
|
+
const e = data && data.error;
|
|
252
|
+
if (e && typeof e === 'object') return e.code || '';
|
|
253
|
+
if (typeof e === 'string') return e;
|
|
254
|
+
return (data && data.code) || '';
|
|
255
|
+
}
|
|
256
|
+
function errMessage(data) {
|
|
257
|
+
const e = data && data.error;
|
|
258
|
+
if (e && typeof e === 'object' && e.message) return e.message;
|
|
259
|
+
return (data && data.message) || '';
|
|
260
|
+
}
|
|
261
|
+
// Structured payload. Falls back to the BODY itself on the flat shape, where
|
|
262
|
+
// the extra fields (conflicts, failures, what_to_do) sat at the top level.
|
|
263
|
+
function errDetails(data) {
|
|
264
|
+
const e = data && data.error;
|
|
265
|
+
if (e && typeof e === 'object' && e.details && typeof e.details === 'object') return e.details;
|
|
266
|
+
// ALWAYS an object, so every caller can read `det.conflicts` with no type
|
|
267
|
+
// guard. A non-object body (a string, a number) would otherwise leak through
|
|
268
|
+
// and turn a detail read into undefined-or-worse at the call site.
|
|
269
|
+
return (data && typeof data === 'object') ? data : {};
|
|
270
|
+
}
|
|
271
|
+
|
|
233
272
|
window.OTB = {
|
|
234
273
|
escapeHtml, $, fmtNum, num, fmtInt, fmtCompact, fmtDate, rankLabel, toast, moduleOn,
|
|
274
|
+
errCode, errMessage, errDetails, // task 1002462 — ADR 0116 envelope readers
|
|
235
275
|
contributeSettingsPanel, settingsPanels,
|
|
236
276
|
contributeHallWidget, hallWidgets, hallWidgetIds,
|
|
237
277
|
splitCriterion,
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// builds through unchanged; result-mode ({ status, ok, data }) preserves control flow 1:1.
|
|
22
22
|
const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
23
23
|
const AUTH_NEEDED = 'auth-needed';
|
|
24
|
-
const { escapeHtml, $, fmtDate } = window.OTB;
|
|
24
|
+
const { escapeHtml, $, fmtDate, errCode, errMessage } = window.OTB; // task 1002462: envelope readers
|
|
25
25
|
const toast = (msg) => window.OTB.toast(msg, { duration: 2600 });
|
|
26
26
|
const curLabel = () => (window.__BRANDING__ && window.__BRANDING__.currency && window.__BRANDING__.currency.label) || 'credits';
|
|
27
27
|
|
|
@@ -70,13 +70,19 @@
|
|
|
70
70
|
// exactly those POST /tasks/:id/ship + /confirm can return. The server is the
|
|
71
71
|
// authority (ADR 0016); the UI only surfaces the refusal.
|
|
72
72
|
function friendlyActionError(status, data) {
|
|
73
|
-
|
|
73
|
+
// task 1002462: read the ADR 0116 envelope. `data.error` is an OBJECT here,
|
|
74
|
+
// so every case fell through to the generic default and a precise lifecycle
|
|
75
|
+
// refusal ("not confirmed yet", "attestation required") rendered as
|
|
76
|
+
// "Could not complete that (409)".
|
|
77
|
+
switch (errCode(data)) {
|
|
74
78
|
case 'task_not_confirmed':
|
|
75
79
|
return 'That task can only be shipped once it is confirmed (its grade has landed).';
|
|
76
80
|
case 'task_not_completed':
|
|
77
81
|
return 'That task is not marked complete yet.';
|
|
78
82
|
case 'verify_attestation_required':
|
|
79
|
-
|
|
83
|
+
// The server's own sentence is the useful one — it names the task and the
|
|
84
|
+
// flag. It sits at error.message under the envelope, not data.message.
|
|
85
|
+
return errMessage(data) || 'Confirm a verification task only after the verify actually passed.';
|
|
80
86
|
case 'rank_too_low_for_task':
|
|
81
87
|
case 'INSUFFICIENT_RANK':
|
|
82
88
|
return 'Your rank is not high enough for that action.';
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
// window.OTB toolkit (dom-utils.js, loaded first). Thin wrappers keep this
|
|
36
36
|
// file's exact variants (date-only fmtDate; a 2200ms clearing toast).
|
|
37
37
|
// fmtAgo / toAuth / getJSON / postJSON stay local below.
|
|
38
|
-
const { escapeHtml, $, fmtNum } = window.OTB;
|
|
38
|
+
const { escapeHtml, $, fmtNum, errCode, errDetails } = window.OTB; // task 1002462: envelope readers
|
|
39
39
|
// Shared task-domain helpers (task 1002463). These lived in two copies here and
|
|
40
40
|
// in the other board and had already drifted — goals-page.js had silently lost
|
|
41
41
|
// friendlyBatchReason's MODULE_CONFLICT case. Destructured so every call site
|
|
@@ -468,14 +468,18 @@
|
|
|
468
468
|
// Map the server's typed error code to a builder-readable line. The codes are
|
|
469
469
|
// exactly those POST /claims and POST /claims/:id/resolve can return.
|
|
470
470
|
function friendlyActionError(status, data) {
|
|
471
|
-
|
|
472
|
-
|
|
471
|
+
// task 1002462: read the ADR 0116 envelope. `data.error` is an OBJECT, so the
|
|
472
|
+
// old `const code = data && data.error` compared a string to an object and
|
|
473
|
+
// EVERY case below fell through to the generic default — a wrong-but-plausible
|
|
474
|
+
// toast on every claim/release refusal.
|
|
475
|
+
const det = errDetails(data);
|
|
476
|
+
switch (errCode(data)) {
|
|
473
477
|
case 'ALREADY_CLAIMED':
|
|
474
478
|
return 'Someone else claimed that task first. The board will refresh.';
|
|
475
479
|
case 'TASK_NOT_READY':
|
|
476
480
|
return 'That task is no longer ready to be claimed.';
|
|
477
481
|
case 'TOUCHES_CONFLICT': {
|
|
478
|
-
const titles = Array.isArray(
|
|
482
|
+
const titles = Array.isArray(det.conflicts) ? det.conflicts.map((c) => c.title).filter(Boolean) : [];
|
|
479
483
|
return titles.length
|
|
480
484
|
? `That task overlaps an active claim: ${titles.join('; ')}. Wait for it to ship.`
|
|
481
485
|
: 'That task overlaps another active claim. Wait for it to ship.';
|
|
@@ -487,7 +491,7 @@
|
|
|
487
491
|
case 'INSUFFICIENT_RANK':
|
|
488
492
|
return 'Your rank is not yet high enough for that task.';
|
|
489
493
|
case 'newcomer_must_ship_first':
|
|
490
|
-
return
|
|
494
|
+
return det.what_to_do || 'Ship your current claim before opening another.';
|
|
491
495
|
case 'NOT_YOUR_CLAIM':
|
|
492
496
|
return 'That claim is not yours to release.';
|
|
493
497
|
case 'ALREADY_RESOLVED':
|
|
@@ -753,8 +757,13 @@
|
|
|
753
757
|
await refreshLive();
|
|
754
758
|
} catch (err) {
|
|
755
759
|
if (err && err.message === 'redirecting to auth') return;
|
|
756
|
-
|
|
757
|
-
|
|
760
|
+
// task 1002462: same envelope defect — this tested `err.data.error === 'batch_failed'`
|
|
761
|
+
// against an object, so an all-or-nothing batch refusal rendered as the generic toast
|
|
762
|
+
// and the per-task reasons were dropped on the floor.
|
|
763
|
+
if (err.status === 409 && errCode(err.data) === 'batch_failed') {
|
|
764
|
+
const bdet = errDetails(err.data);
|
|
765
|
+
const failures = Array.isArray(bdet.failures) ? bdet.failures
|
|
766
|
+
: (Array.isArray(err.data && err.data.failures) ? err.data.failures : []);
|
|
758
767
|
const lines = failures.map((f) => `#${f.task_id} (${friendlyBatchReason(f)})`);
|
|
759
768
|
toast(lines.length
|
|
760
769
|
? `The batch could not be claimed — ${lines.join('; ')}.`
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// modules/lifecycle/est-advisory.js — the ESTIMATE-SCALE advisory (task 1003148).
|
|
2
|
+
//
|
|
3
|
+
// THE HOLE. Nothing tells a task author that their estimate is on the wrong SCALE.
|
|
4
|
+
// Across 1,173 shipped tasks carrying both est_minutes and actual_minutes, the
|
|
5
|
+
// median actual/est ratio is 0.100 — and it is uniform across every kind with a
|
|
6
|
+
// real sample (feature 0.078/n=489, bug 0.150/n=214, infra 0.097/n=144,
|
|
7
|
+
// cleanup 0.133/n=89, decision 0.100/n=76, refactor 0.120/n=62, spike 0.142/n=36).
|
|
8
|
+
// That is not per-task noise. Authors write HUMAN-HOURS-shaped numbers for work an
|
|
9
|
+
// agent does in minutes.
|
|
10
|
+
//
|
|
11
|
+
// pms_calibrated_minutes (migration 015) already divides by that median so
|
|
12
|
+
// /builder-start and the optimizer show a sane figure. But the STORED number stays
|
|
13
|
+
// wrong, nobody is ever told, and every newly-filed task re-adds the same error —
|
|
14
|
+
// so the calibration is a permanent correction for a mistake with no feedback
|
|
15
|
+
// loop. It also misleads humans directly: GET /tasks/:id leads with the raw field,
|
|
16
|
+
// and a session quoted task 1002572 to the owner as "60 min" when its calibrated
|
|
17
|
+
// value is 9.
|
|
18
|
+
//
|
|
19
|
+
// This module closes the loop WITHOUT blocking anything: POST /tasks attaches the
|
|
20
|
+
// verdict to its 201 body as `est_advisory`, and an in-scale estimate gets `null` —
|
|
21
|
+
// silence. Deliberately NOT an auto-correction: silently rewriting the author's
|
|
22
|
+
// number is worse than the current silence, because then nobody learns the scale
|
|
23
|
+
// either. Show the figure, name the evidence, let the author decide.
|
|
24
|
+
//
|
|
25
|
+
// PURE core + FAIL-OPEN wrapper, exported so every branch is pinned by a DB-free
|
|
26
|
+
// unit test. A module sibling, like ./goal-advisory.js — same contract.
|
|
27
|
+
|
|
28
|
+
// WHY THIS IS AN ABSOLUTE DELTA AND NOT A RATIO — a design flaw my own test
|
|
29
|
+
// caught, worth recording so it is not reintroduced. The calibration is a pure
|
|
30
|
+
// scalar multiply, so submitted / (submitted x ratio) === 1 / ratio for EVERY
|
|
31
|
+
// input. A ratio-based "how inflated is this?" test therefore returns the same
|
|
32
|
+
// answer for a correct 9-minute estimate as for a wrong 600-minute one — it fires
|
|
33
|
+
// on everything, which is the noise failure this advisory must avoid. (It did:
|
|
34
|
+
// est=9 for a bug floored to calibrated=1 and read as "9x over".)
|
|
35
|
+
//
|
|
36
|
+
// So the trigger is whether the TRANSLATION IS MATERIAL: how many minutes the
|
|
37
|
+
// author's number and the calibrated equivalent actually differ by. A 9-minute
|
|
38
|
+
// estimate translates to ~1 (delta 8 — not worth a word); a 300-minute estimate
|
|
39
|
+
// translates to 45 (delta 255 — worth saying out loud).
|
|
40
|
+
//
|
|
41
|
+
// AND BE PRECISE ABOUT THE FEEDBACK LOOP, because the tempting claim is false:
|
|
42
|
+
// adopting the suggested value does NOT silence this for that task. The
|
|
43
|
+
// calibration is a constant multiply, so the new number gets translated too. The
|
|
44
|
+
// loop closes at the POPULATION level — pms_calibration_summary recomputes the
|
|
45
|
+
// median from shipped rows, so as authors write agent-minutes the ratio itself
|
|
46
|
+
// climbs toward 1.0 and the delta collapses for everyone at once. That is the
|
|
47
|
+
// only mechanism that can actually retire this advisory, and it is why the
|
|
48
|
+
// advisory reports the ratio and its sample size rather than a verdict.
|
|
49
|
+
const MIN_MATERIAL_DELTA = 15;
|
|
50
|
+
|
|
51
|
+
// TWO conditions, because the delta alone is partly a "large estimate" detector:
|
|
52
|
+
// with a mostly-corrected population (ratio 0.9) a 300-minute estimate still
|
|
53
|
+
// differs by 30. This advisory is about SCALE, so it also requires the scale to
|
|
54
|
+
// actually be off. Above this ratio, estimates and actuals broadly agree and there
|
|
55
|
+
// is nothing to translate no matter how big the number is — which is what lets the
|
|
56
|
+
// population feedback loop retire the advisory outright.
|
|
57
|
+
const SCALE_OK_RATIO = 0.5;
|
|
58
|
+
|
|
59
|
+
// Below this many samples for a kind, that kind's median is not worth quoting and
|
|
60
|
+
// the overall median is used instead — mirroring pms_calibrated_minutes' own
|
|
61
|
+
// thresholds (>=5 per kind, else >=10 overall) so the advisory and the calibration
|
|
62
|
+
// can never disagree about which number applies.
|
|
63
|
+
const MIN_KIND_SAMPLES = 5;
|
|
64
|
+
const MIN_OVERALL_SAMPLES = 10;
|
|
65
|
+
|
|
66
|
+
// PURE. Pick the median ratio that governs this kind, and say which population it
|
|
67
|
+
// came from — the author should be able to tell "your kind, 214 samples" from
|
|
68
|
+
// "everything, 1173 samples".
|
|
69
|
+
function pickRatio({ kind, summary }) {
|
|
70
|
+
const rows = Array.isArray(summary) ? summary : [];
|
|
71
|
+
const forKind = rows.find((r) => r && r.kind === kind);
|
|
72
|
+
if (forKind && Number(forKind.sample_size) >= MIN_KIND_SAMPLES) {
|
|
73
|
+
const ratio = Number(forKind.median_ratio);
|
|
74
|
+
if (Number.isFinite(ratio) && ratio > 0) {
|
|
75
|
+
return { ratio, sample_size: Number(forKind.sample_size), basis: kind };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Overall: a sample-weighted median is not reconstructible from per-kind medians,
|
|
79
|
+
// so use the median OF the per-kind medians weighted by nothing — an honest
|
|
80
|
+
// approximation, labelled as such. Callers that need the exact overall figure
|
|
81
|
+
// should read pms_calibration_summary() directly.
|
|
82
|
+
const usable = rows
|
|
83
|
+
.map((r) => ({ ratio: Number(r && r.median_ratio), n: Number(r && r.sample_size) }))
|
|
84
|
+
.filter((r) => Number.isFinite(r.ratio) && r.ratio > 0 && Number.isFinite(r.n) && r.n > 0);
|
|
85
|
+
const total = usable.reduce((s, r) => s + r.n, 0);
|
|
86
|
+
if (total < MIN_OVERALL_SAMPLES || usable.length === 0) return null;
|
|
87
|
+
const sorted = usable.map((r) => r.ratio).sort((a, b) => a - b);
|
|
88
|
+
const mid = Math.floor(sorted.length / 2);
|
|
89
|
+
const ratio = sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
90
|
+
return { ratio, sample_size: total, basis: 'all kinds' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// PURE, FAIL-OPEN. Returns null when there is nothing to say: no estimate given,
|
|
94
|
+
// no usable history, or an estimate already in the right scale.
|
|
95
|
+
function adviseEstimate({ estMinutes, kind, summary, taskId = null }) {
|
|
96
|
+
const est = Number(estMinutes);
|
|
97
|
+
if (!Number.isFinite(est) || est <= 0) return null; // nothing to judge
|
|
98
|
+
const picked = pickRatio({ kind, summary });
|
|
99
|
+
if (!picked) return null; // not enough history to make a claim
|
|
100
|
+
if (picked.ratio >= SCALE_OK_RATIO) return null; // the scale is fine — nothing to say
|
|
101
|
+
const calibrated = Math.max(1, Math.round(est * picked.ratio));
|
|
102
|
+
if (est - calibrated < MIN_MATERIAL_DELTA) return null; // translation immaterial — say nothing
|
|
103
|
+
const ratio = Math.round(picked.ratio * 1000) / 1000;
|
|
104
|
+
// The historical factor is a property of the KIND, not of this estimate — see
|
|
105
|
+
// the note on MIN_MATERIAL_DELTA. Report it as such rather than implying we
|
|
106
|
+
// measured this particular guess.
|
|
107
|
+
const kindFactor = Math.round((1 / picked.ratio) * 10) / 10;
|
|
108
|
+
return {
|
|
109
|
+
submitted_minutes: est,
|
|
110
|
+
calibrated_minutes: calibrated,
|
|
111
|
+
kind_factor: kindFactor,
|
|
112
|
+
median_ratio: ratio,
|
|
113
|
+
sample_size: picked.sample_size,
|
|
114
|
+
basis: picked.basis,
|
|
115
|
+
message: `est_minutes=${est} translates to ~${calibrated} min of real time. Across `
|
|
116
|
+
+ `${picked.sample_size} shipped task(s) (${picked.basis}) the median actual/est ratio is `
|
|
117
|
+
+ `${ratio}, i.e. estimates of this kind have run ~${kindFactor}x long — est_minutes is `
|
|
118
|
+
+ 'AGENT-minutes, not human-hours. The calibration corrects the board, but the STORED number '
|
|
119
|
+
+ 'is what a reader sees.',
|
|
120
|
+
next_step: taskId
|
|
121
|
+
? `If ${calibrated} is closer, correct it: PATCH /tasks/${taskId} {"est_minutes": ${calibrated}}`
|
|
122
|
+
: `Consider est_minutes: ${calibrated}.`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The calibration population. Reads pms_calibration_summary() — the transparency
|
|
127
|
+
// companion migration 015 shipped for exactly this — rather than recomputing the
|
|
128
|
+
// medians, so this and pms_calibrated_minutes can never disagree about the ratio.
|
|
129
|
+
// Lives HERE rather than in db.js: db.js is over the size ratchet's cap and this
|
|
130
|
+
// is the only caller. The pool comes through the published module doorway.
|
|
131
|
+
// Deliberately not fail-open — buildEstAdvisory below is the single failure site.
|
|
132
|
+
async function calibrationSummary({ pool } = {}) {
|
|
133
|
+
const p = pool || require('../../src/module-api').getPool();
|
|
134
|
+
const { rows } = await p.query('SELECT kind, median_ratio, sample_size FROM pms_calibration_summary()');
|
|
135
|
+
return rows || [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The DB-touching wrapper. NON-BLOCKING BY CONSTRUCTION — any failure yields null
|
|
139
|
+
// and the create still returns 201. Same contract as buildGoalAdvisory. `db` is
|
|
140
|
+
// accepted for symmetry with that function and to let a test inject a summary.
|
|
141
|
+
async function buildEstAdvisory({ db, task } = {}) {
|
|
142
|
+
try {
|
|
143
|
+
if (!task || task.est_minutes == null) return null;
|
|
144
|
+
const summary = (db && db.calibrationSummary) ? await db.calibrationSummary() : await calibrationSummary();
|
|
145
|
+
return adviseEstimate({
|
|
146
|
+
estMinutes: task.est_minutes,
|
|
147
|
+
kind: task.kind,
|
|
148
|
+
summary,
|
|
149
|
+
taskId: task.id,
|
|
150
|
+
});
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// Through the published logger seam (api.logger), not a raw console call: a
|
|
153
|
+
// module logs via the doorway, and the console_call_count ratchet
|
|
154
|
+
// (config/fitness-baselines.json) holds at its recorded floor. Lazy-required
|
|
155
|
+
// so the DB-free unit test that exercises THIS branch stays hermetic.
|
|
156
|
+
require('../../src/module-api').logger('lifecycle')
|
|
157
|
+
.error('est-advisory (non-blocking)', e && e.message);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = { adviseEstimate, pickRatio, buildEstAdvisory, calibrationSummary, MIN_MATERIAL_DELTA, SCALE_OK_RATIO, MIN_KIND_SAMPLES, MIN_OVERALL_SAMPLES };
|