@bongos/core 1.20.65 → 1.20.67

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.
@@ -2787,5 +2787,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2787
2787
  landed since 1.20.63 with no explicit bump. run 36851876428. (task 1002620)
2788
2788
  1.20.65 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2789
2789
  landed since 1.20.64 with no explicit bump. run 36852902728. (task 1002620)
2790
+ 1.20.66 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2791
+ landed since 1.20.65 with no explicit bump. run 36854308972. (task 1002620)
2792
+ 1.20.67 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2793
+ landed since 1.20.66 with no explicit bump. run 36860726629. (task 1002620)
2790
2794
  ---------------------------------------------------------------------------
2791
2795
  ```
@@ -1085,7 +1085,7 @@
1085
1085
  "surface": "builders",
1086
1086
  "title": "Goals",
1087
1087
  "states_read": ["list","filters","cards","detail","out","uat","uat-panel"],
1088
- "files_hash": "200dea926de32610",
1088
+ "files_hash": "bec6cb43b659cd69",
1089
1089
  "reading_hash": "2104acb45018ce47",
1090
1090
  "counts": {"lines":171,"placed":46,"shared":5,"unplaced":120},
1091
1091
  "omitted": {"data":251},
@@ -1136,7 +1136,7 @@
1136
1136
  {"key":"L0043","section":"Members0","text":"No members yet — be the first to join.","placement":"unplaced","state":"detail"},
1137
1137
  {"key":"L0044","section":"Conflicts12","text":"Conflicts","placement":"unplaced","state":"detail"},
1138
1138
  {"key":"L0045","section":"Conflicts12","text":"Open tasks whose declared files overlap — sequence these before two builders collide.","placement":"placed","file":"modules/hall-ui/public/goals.html","line":117,"string_id":"99c630009152","state":"detail"},
1139
- {"key":"L0046","section":"Conflicts12","text":"View all {…} {…} →","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":611,"string_id":"9b92aa01516d","state":"detail"},
1139
+ {"key":"L0046","section":"Conflicts12","text":"View all {…} {…} →","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":617,"string_id":"9b92aa01516d","state":"detail"},
1140
1140
  {"key":"L0047","section":"Task {…}","text":"Task dependencies","placement":"placed","file":"modules/hall-ui/public/goals.html","line":132,"string_id":"a0f384b022fd","state":"detail"},
1141
1141
  {"key":"L0048","section":"Task {…}","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","state":"detail"},
1142
1142
  {"key":"L0049","section":"Task {…}","text":"#1165 Blocker-solve","placement":"unplaced","state":"detail"},
@@ -1151,7 +1151,7 @@
1151
1151
  {"key":"L0058","section":"Task {…}","text":"converge onto the kit…","placement":"unplaced","state":"detail"},
1152
1152
  {"key":"L0059","section":"Task {…}","text":"#2172 Settings","placement":"unplaced","state":"detail"},
1153
1153
  {"key":"L0060","section":"Task {…}","text":"regroup: 6 groups, st…","placement":"unplaced","state":"detail"},
1154
- {"key":"L0061","section":"Task {…}","text":"Task","placement":"placed","file":"modules/hall-ui/public/task.js","line":503,"string_id":"eff70e59d12e","state":"detail"},
1154
+ {"key":"L0061","section":"Task {…}","text":"Task","placement":"placed","file":"modules/hall-ui/public/task.js","line":512,"string_id":"eff70e59d12e","state":"detail"},
1155
1155
  {"key":"L0062","section":"Task {…}","text":"Status","placement":"unplaced","state":"detail"},
1156
1156
  {"key":"L0063","section":"Task {…}","text":"Priority","placement":"unplaced","state":"detail"},
1157
1157
  {"key":"L0064","section":"Task {…}","text":"Waits on","placement":"placed","file":"modules/hall-ui/public/goal-forest-render.js","line":146,"string_id":"8e4f0179e1d3","state":"detail"},
@@ -2808,25 +2808,25 @@
2808
2808
  "surface": "builders",
2809
2809
  "title": "Task",
2810
2810
  "states_read": ["record","from-goal","missing"],
2811
- "files_hash": "6634cc912a392475",
2811
+ "files_hash": "e01a05d46624d7f0",
2812
2812
  "reading_hash": "66f99a3663a75a77",
2813
2813
  "counts": {"lines":117,"placed":29,"shared":5,"unplaced":83},
2814
2814
  "omitted": {"data":67},
2815
2815
  "notes": [],
2816
2816
  "lines": [
2817
2817
  {"key":"L0001","section":"skip link","text":"Skip to content","placement":"shared","file":"modules/hall-ui/public/shell.js","line":884,"string_id":"87cee78ca134"},
2818
- {"key":"L0002","section":"top bar","text":"Task","placement":"shared","file":"modules/hall-ui/public/task.js","line":503,"string_id":"eff70e59d12e"},
2818
+ {"key":"L0002","section":"top bar","text":"Task","placement":"shared","file":"modules/hall-ui/public/task.js","line":512,"string_id":"eff70e59d12e"},
2819
2819
  {"key":"L0003","section":"top bar","text":"Jump to…","placement":"shared","file":"modules/hall-ui/public/shell.js","line":495,"string_id":"eba610d25523"},
2820
2820
  {"key":"L0004","section":"top bar","text":"Ctrl K","placement":"shared"},
2821
2821
  {"key":"L0005","section":"page top","text":"Back to {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":226,"string_id":"f6bb75b08e98","state":"from-goal","occurrences":2},
2822
- {"key":"L0006","section":"page top","text":"Task {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":503,"string_id":"eff70e59d12e"},
2823
- {"key":"L0007","section":"page top","text":"Task","placement":"placed","file":"modules/hall-ui/public/task.js","line":503,"string_id":"eff70e59d12e"},
2824
- {"key":"L0008","section":"page top","text":"shipped","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":822,"string_id":"bd3402efa59e"},
2822
+ {"key":"L0006","section":"page top","text":"Task {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":512,"string_id":"eff70e59d12e"},
2823
+ {"key":"L0007","section":"page top","text":"Task","placement":"placed","file":"modules/hall-ui/public/task.js","line":512,"string_id":"eff70e59d12e"},
2824
+ {"key":"L0008","section":"page top","text":"shipped","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":828,"string_id":"bd3402efa59e"},
2825
2825
  {"key":"L0009","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"· {…} · {…} · {…} to any rank","placement":"unplaced"},
2826
2826
  {"key":"L0010","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Created {…}, 78d ago.","placement":"unplaced"},
2827
2827
  {"key":"L0011","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Goal","placement":"placed","file":"modules/hall-ui/public/gate.js","line":288,"string_id":"ec88b946ad16"},
2828
2828
  {"key":"L0012","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":8},
2829
- {"key":"L0013","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Move to goal","placement":"placed","file":"modules/hall-ui/public/task.js","line":590,"string_id":"e52ae74f0052"},
2829
+ {"key":"L0013","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Move to goal","placement":"placed","file":"modules/hall-ui/public/task.js","line":599,"string_id":"e52ae74f0052"},
2830
2830
  {"key":"L0014","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Move","placement":"unplaced"},
2831
2831
  {"key":"L0015","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Priority","placement":"unplaced","occurrences":2},
2832
2832
  {"key":"L0016","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"— unset —","placement":"unplaced"},
@@ -2835,11 +2835,11 @@
2835
2835
  {"key":"L0019","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"P3","placement":"unplaced"},
2836
2836
  {"key":"L0020","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"P4","placement":"unplaced"},
2837
2837
  {"key":"L0021","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"P5","placement":"unplaced"},
2838
- {"key":"L0022","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Wait for task","placement":"placed","file":"modules/hall-ui/public/task.js","line":620,"string_id":"411b2ff58094"},
2839
- {"key":"L0023","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"task id","placement":"placed","file":"modules/hall-ui/public/task.js","line":620,"string_id":"3906fbc07bbe","attr":"placeholder"},
2840
- {"key":"L0024","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Set priority","placement":"placed","file":"modules/hall-ui/public/task.js","line":620,"string_id":"c11213056bab"},
2841
- {"key":"L0025","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Add dependency","placement":"placed","file":"modules/hall-ui/public/task.js","line":620,"string_id":"2b94171ce525"},
2842
- {"key":"L0026","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"A dependency keeps this task out of the claimable queue until that one ships. Remove one with the × beside it under “Blocked by”.","placement":"placed","file":"modules/hall-ui/public/task.js","line":620,"string_id":"518e5acc9184"},
2838
+ {"key":"L0022","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Wait for task","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"411b2ff58094"},
2839
+ {"key":"L0023","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"task id","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"3906fbc07bbe","attr":"placeholder"},
2840
+ {"key":"L0024","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Set priority","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"c11213056bab"},
2841
+ {"key":"L0025","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Add dependency","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"2b94171ce525"},
2842
+ {"key":"L0026","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"A dependency keeps this task out of the claimable queue until that one ships. Remove one with the × beside it under “Blocked by”.","placement":"placed","file":"modules/hall-ui/public/task.js","line":629,"string_id":"518e5acc9184"},
2843
2843
  {"key":"L0027","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Estimated","placement":"unplaced"},
2844
2844
  {"key":"L0028","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"m","placement":"unplaced","occurrences":2},
2845
2845
  {"key":"L0029","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Actual","placement":"unplaced"},
@@ -2853,7 +2853,7 @@
2853
2853
  {"key":"L0037","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Credit","placement":"unplaced"},
2854
2854
  {"key":"L0038","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"80 {…} landed","placement":"unplaced"},
2855
2855
  {"key":"L0039","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"2 claims","placement":"unplaced"},
2856
- {"key":"L0040","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Last claimed by","placement":"placed","file":"modules/hall-ui/public/task.js","line":670,"string_id":"13bae4dfb28e"},
2856
+ {"key":"L0040","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Last claimed by","placement":"placed","file":"modules/hall-ui/public/task.js","line":679,"string_id":"13bae4dfb28e"},
2857
2857
  {"key":"L0041","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"on {…}, {…} released, outcome {…}.","placement":"unplaced"},
2858
2858
  {"key":"L0042","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Value","placement":"unplaced"},
2859
2859
  {"key":"L0043","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Description","placement":"unplaced"},
@@ -2910,26 +2910,26 @@
2910
2910
  {"key":"L0094","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"on edited JS before ship.","placement":"unplaced"},
2911
2911
  {"key":"L0095","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Touches","placement":"unplaced"},
2912
2912
  {"key":"L0096","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Blocks","placement":"unplaced"},
2913
- {"key":"L0097","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Shipped","placement":"placed","file":"modules/hall-ui/public/task.js","line":705,"string_id":"0c0a50ba1666"},
2914
- {"key":"L0098","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Shipped {…}{…}, {…}.","placement":"placed","file":"modules/hall-ui/public/task.js","line":705,"string_id":"0c0a50ba1666"},
2915
- {"key":"L0099","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"The adversarial panel. Each worker reports pass, concerns or fail on its own; the gating workers must all pass before confirmed becomes {…}, and one concerns falls the task through to the manual confirm path.","placement":"placed","file":"modules/hall-ui/public/task.js","line":440,"string_id":"6e93ef49d63f"},
2916
- {"key":"L0100","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"verdict {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":444,"string_id":"4de556377db2"},
2913
+ {"key":"L0097","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Shipped","placement":"placed","file":"modules/hall-ui/public/task.js","line":714,"string_id":"0c0a50ba1666"},
2914
+ {"key":"L0098","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Shipped {…}{…}, {…}.","placement":"placed","file":"modules/hall-ui/public/task.js","line":714,"string_id":"0c0a50ba1666"},
2915
+ {"key":"L0099","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"The adversarial panel. Each worker reports pass, concerns or fail on its own; the gating workers must all pass before confirmed becomes {…}, and one concerns falls the task through to the manual confirm path.","placement":"placed","file":"modules/hall-ui/public/task.js","line":449,"string_id":"6e93ef49d63f"},
2916
+ {"key":"L0100","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"verdict {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":453,"string_id":"4de556377db2"},
2917
2917
  {"key":"L0101","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"threshold 6 · panel {…} · {…}","placement":"unplaced"},
2918
- {"key":"L0102","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Session log{…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":496,"string_id":"55e0a34bafed"},
2918
+ {"key":"L0102","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Session log{…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":505,"string_id":"55e0a34bafed"},
2919
2919
  {"key":"L0103","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Session","placement":"unplaced"},
2920
2920
  {"key":"L0104","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":", 37m","placement":"unplaced"},
2921
2921
  {"key":"L0105","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Outcome","placement":"unplaced"},
2922
- {"key":"L0106","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"shipped","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":822,"string_id":"bd3402efa59e"},
2923
- {"key":"L0107","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"{…} awarded","placement":"placed","file":"modules/hall-ui/public/task.js","line":487,"string_id":"788ff11f1135"},
2922
+ {"key":"L0106","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"shipped","placement":"placed","file":"modules/hall-ui/public/goals-render.js","line":828,"string_id":"bd3402efa59e"},
2923
+ {"key":"L0107","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"{…} awarded","placement":"placed","file":"modules/hall-ui/public/task.js","line":496,"string_id":"788ff11f1135"},
2924
2924
  {"key":"L0108","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Handoff","placement":"unplaced"},
2925
- {"key":"L0109","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Read the handoff notes","placement":"placed","file":"modules/hall-ui/public/task.js","line":490,"string_id":"c5f19a888a15"},
2926
- {"key":"L0110","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Full log","placement":"placed","file":"modules/hall-ui/public/task.js","line":492,"string_id":"fc6fdfa639a7"},
2927
- {"key":"L0111","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Open in Sessions","placement":"placed","file":"modules/hall-ui/public/task.js","line":492,"string_id":"5f3e0b179839"},
2925
+ {"key":"L0109","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Read the handoff notes","placement":"placed","file":"modules/hall-ui/public/task.js","line":499,"string_id":"c5f19a888a15"},
2926
+ {"key":"L0110","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Full log","placement":"placed","file":"modules/hall-ui/public/task.js","line":501,"string_id":"fc6fdfa639a7"},
2927
+ {"key":"L0111","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Open in Sessions","placement":"placed","file":"modules/hall-ui/public/task.js","line":501,"string_id":"5f3e0b179839"},
2928
2928
  {"key":"L0112","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Actions","placement":"unplaced"},
2929
- {"key":"L0113","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"This task has shipped and its credits have landed. Neither Ship it nor Confirm is available.","placement":"placed","file":"modules/hall-ui/public/task.js","line":742,"string_id":"c9e50a67988a"},
2930
- {"key":"L0114","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Copy prompt","placement":"placed","file":"modules/hall-ui/public/task.js","line":818,"string_id":"a873e7c29d80"},
2929
+ {"key":"L0113","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"This task has shipped and its credits have landed. Neither Ship it nor Confirm is available.","placement":"placed","file":"modules/hall-ui/public/task.js","line":765,"string_id":"c9e50a67988a"},
2930
+ {"key":"L0114","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Copy prompt","placement":"placed","file":"modules/hall-ui/public/task.js","line":841,"string_id":"a873e7c29d80"},
2931
2931
  {"key":"L0115","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"Back to {…}","placement":"placed","file":"modules/hall-ui/public/task.js","line":226,"string_id":"f6bb75b08e98","state":"from-goal","occurrences":2},
2932
- {"key":"L0116","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"A shipped task cannot be retired — it is immutable history now.","placement":"placed","file":"modules/hall-ui/public/task.js","line":774,"string_id":"f3d6ed7c0d2c"},
2932
+ {"key":"L0116","section":"2166hall-kit.js: shared hall UI primitives (pager, ledger, filter bar, tabs, ra…","text":"A shipped task cannot be retired — it is immutable history now.","placement":"placed","file":"modules/hall-ui/public/task.js","line":797,"string_id":"f3d6ed7c0d2c"},
2933
2933
  {"key":"L0117","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":471,"string_id":"1883ad7f07fc"}
2934
2934
  ]
2935
2935
  },
@@ -152,7 +152,7 @@ const PERMISSIONS = [
152
152
  // granted is not a gate" argument. That argument was heard and was not the deciding
153
153
  // one. The revisit trigger is a concrete one — the first time such a rank exists —
154
154
  // and tests/government_catalog.mjs pins the flag so the change cannot land silently.
155
- { key: 'version.create', system: false, floor: 'archon', guards: 'POST /versions — create a version + its done-when criteria (ADR 0250 §6 returns it to archon; ADR 0157 had lowered it to metic)' },
155
+ { key: 'version.create', system: false, floor: 'archon', guards: 'POST /versions — create a version + its done-when criteria (ADR 0250 §6 returns it to archon; ADR 0157 had lowered it to metic); POST /goals/:id/move — re-home a goal across versions (task 1004485)' },
156
156
  { key: 'task.confirm.grade_bypass', system: true, floor: 'archon', guards: 'POST /tasks/:id/confirm — ratify past a failed grade (env-relaxable, ADR 0041)' },
157
157
  { key: 'dependency.cross_tier.manage', system: true, floor: 'archon', guards: 'POST|DELETE /dependencies — cross-tier structural edges (ADR 0086)' },
158
158
  { key: 'goal.scope.manage', system: true, floor: 'archon', guards: 'cross-tier goal-scope edges + protected-scope admission (ADR 0086)' },
@@ -47,18 +47,50 @@
47
47
  // goal's tasks into these buckets:
48
48
  // done — settled (shipped/confirmed/completed)
49
49
  // active — a live claim ("in progress")
50
- // ready — claimable now (deps met + promoted)
51
- // blocked — backlog with ≥1 unshipped dependency
52
- // backlog — backlog, no unmet deps (just not started/promoted)
50
+ // ready — claimable now (promoted AND every dependency shipped)
51
+ // blocked — unclaimed with ≥1 unshipped dependency, WHATEVER the row says
52
+ // backlog — not promoted, no unmet deps (just not started)
53
+
54
+ // hasUnmetDeps — the claim gate's own question, asked in the browser: does
55
+ // this task wait on an edge whose target has not shipped? `blocked_by[]`
56
+ // already carries the per-edge `satisfied` flag (attachTaskDepSummaries,
57
+ // satisfied === target.status === 'shipped'), which is the same rule
58
+ // `POST /claims` runs before it refuses with DEPS_NOT_SHIPPED (ADR 0015).
59
+ // Task→task edges only, like the array it reads — the goal/criterion arms of
60
+ // CLAIMABLE_SQL's guard have no client-side shape to read, so this is a
61
+ // subset: it never calls a claimable task blocked, it only stops short of
62
+ // catching every blocked one.
63
+ const hasUnmetDeps = (t) => ((t && t.blocked_by) || []).some((d) => !d.satisfied);
64
+
53
65
  function readinessOf(t) {
54
66
  const s = t.status;
55
67
  if (DONE_STATUSES.has(s)) return 'done';
56
68
  if (CUT_STATUSES.has(s)) return 'cut';
57
69
  if (s === 'active') return 'active';
70
+ // task 1004487 — ahead of the `ready` arm, and no longer backlog-only. A
71
+ // dependency added after promotion leaves the row at `ready`: ADR 0015
72
+ // rules that the STATUS is deliberately never demoted and the readers are
73
+ // what change (core_264 did it for the claimable feed). Readiness is the
74
+ // derived question "can a builder claim this right now?", so a dep-blocked
75
+ // row belongs in this bucket however the status column reads.
76
+ if (hasUnmetDeps(t)) return 'blocked';
58
77
  if (s === 'ready') return 'ready';
59
- if (s === 'backlog' && (t.blocked_by || []).some((d) => !d.satisfied)) return 'blocked';
60
78
  return 'backlog';
61
79
  }
80
+ // taskStatusTagHtml — the lead tag a goal's task card shows when it is not
81
+ // offering a checkbox (task 1004487). A dep-blocked row reads "blocked" and
82
+ // names what it waits on, rather than echoing a `ready` status column that
83
+ // the claim gate will contradict — the same derivation `readinessOf` makes
84
+ // for the rollup counters, so the card and the goal's numbers agree. Every
85
+ // other row still shows its real status.
86
+ function taskStatusTagHtml(t) {
87
+ const esc = window.OTB.escapeHtml;
88
+ if (!hasUnmetDeps(t)) return `<span class="task__tag" title="Task status">${esc(t.status || '')}</span>`;
89
+ const waiting = (t.blocked_by || []).filter((d) => !d.satisfied)
90
+ .map((d) => `#${d.id} ${d.title || ''}`.trim()).join('\n');
91
+ return `<span class="task__tag" title="Waiting on ${esc(waiting)}">blocked</span>`;
92
+ }
93
+
62
94
  const EMPTY_ROLLUP = { done: 0, active: 0, ready: 0, blocked: 0, backlog: 0, cut: 0, total: 0, nextReady: null };
63
95
 
64
96
  // task 1002439 — a goal row's readiness rollup line (only the non-zero buckets).
@@ -139,7 +171,7 @@
139
171
  window.OTBGoalsLib = {
140
172
  DONE_STATUSES, CUT_STATUSES, isTodoStatus,
141
173
  ring, memberName,
142
- readinessOf, EMPTY_ROLLUP, goalRollLine,
174
+ hasUnmetDeps, taskStatusTagHtml, readinessOf, EMPTY_ROLLUP, goalRollLine,
143
175
  matchesTodoSearch, msOf, taskActivityMs,
144
176
  subtitleAttr, subtitleLine, applyGoalProse,
145
177
  };
@@ -27,7 +27,7 @@
27
27
  const { makePager, paginate, wirePager, pagerBarHtml, makeTabs, makeFilterBar,
28
28
  emptyStateHtml, skeletonRows } = window.OTBKit;
29
29
  const { DONE_STATUSES, CUT_STATUSES, isTodoStatus, ring, memberName,
30
- readinessOf, EMPTY_ROLLUP, goalRollLine, matchesTodoSearch,
30
+ hasUnmetDeps, taskStatusTagHtml, readinessOf, EMPTY_ROLLUP, goalRollLine, matchesTodoSearch,
31
31
  msOf, taskActivityMs, subtitleAttr, subtitleLine, applyGoalProse } = window.OTBGoalsLib;
32
32
  const forestRender = window.OTBGoalForestRender(ctx);
33
33
  // task 1004402 — criterion-uat-ui.js. If that script failed to load, the page
@@ -347,7 +347,11 @@
347
347
  // work is the case that needed it most.
348
348
  const ageChip = window.OTBKit.ageChipHtml(t.created_at);
349
349
  const claim = state.claimsByTask.get(id);
350
- const isReady = t.status === 'ready' && !!state.me;
350
+ // task 1004487 — `ready` is not enough: a dependency added after promotion
351
+ // leaves the row at `ready` and POST /claims still refuses it. Predicate
352
+ // and lead tag are both goals-lib.js's, because both are pure.
353
+ const isReadyNow = t.status === 'ready' && !hasUnmetDeps(t);
354
+ const isReady = isReadyNow && !!state.me;
351
355
  const mineActive = t.status === 'active' && claim && sameId(claim.builder_id, selfId());
352
356
  const releaseKey = mineActive ? String(claim.claim_id) : null;
353
357
  const selected = isReady ? goalSel.claim.has(id) : (mineActive && goalSel.release.has(releaseKey));
@@ -358,7 +362,7 @@
358
362
  ? `<input type="checkbox" class="task__select" data-task-id="${escapeHtml(id)}" aria-label="Select task ${escapeHtml(id)} for bulk claim"${selected ? ' checked' : ''}>`
359
363
  : (mineActive
360
364
  ? `<input type="checkbox" class="wip__select" data-claim-id="${escapeHtml(releaseKey)}" data-task-id="${escapeHtml(id)}" aria-label="Select task ${escapeHtml(id)} for bulk release"${selected ? ' checked' : ''}>`
361
- : `<span class="task__tag" title="Task status">${escapeHtml(t.status || '')}</span>`);
365
+ : taskStatusTagHtml(t));
362
366
  // Actions: ready → Claim + copy; mine-active → Release; other's active →
363
367
  // "claimed · @who"; anything else → nothing (open via the title / card).
364
368
  let actions = '';
@@ -404,7 +408,9 @@
404
408
  const goalSel = { claim: new Set(), release: new Map() };
405
409
  function goalSelTotal() { return goalSel.claim.size + goalSel.release.size; }
406
410
  function pruneGoalSelection() {
407
- const readyIds = new Set(state.work.todo.filter((t) => t.status === 'ready').map((t) => String(t.id)));
411
+ // The same gate the card renders (task 1004487): a tick left on a
412
+ // dep-blocked `ready` row rides into a bulk claim the server refuses.
413
+ const readyIds = new Set(state.work.todo.filter((t) => t.status === 'ready' && !hasUnmetDeps(t)).map((t) => String(t.id)));
408
414
  for (const id of Array.from(goalSel.claim)) if (!readyIds.has(id)) goalSel.claim.delete(id);
409
415
  const mineClaimIds = new Set(
410
416
  state.work.todo
@@ -1092,8 +1098,11 @@
1092
1098
  }
1093
1099
  async function loadGoalTasks(goalId) {
1094
1100
  try {
1101
+ // ?include=deps (task 1004487) — the cards need `blocked_by[]` to tell a
1102
+ // claimable `ready` row from one whose dependencies have not shipped. Two
1103
+ // aggregate queries over ONE goal's tasks, so the cost stays bounded.
1095
1104
  const [resp] = await Promise.all([
1096
- getJSON(`${API}/tasks?goal_id=${encodeURIComponent(goalId)}`),
1105
+ getJSON(`${API}/tasks?goal_id=${encodeURIComponent(goalId)}&include=deps`),
1097
1106
  loadActiveClaims(), // fills state.claimsByTask (best-effort)
1098
1107
  ]);
1099
1108
  if (!sameId(state.current, goalId)) return; // navigated away while loading
@@ -305,8 +305,17 @@
305
305
  function lifecycleRail(t, credits) {
306
306
  const at = RAIL_AT[t.status] ?? 0;
307
307
  const landed = t.status === 'shipped' && credits != null;
308
+ // task 1004487 — the first beat's caption is what tells a reader WHICH of
309
+ // the three unclaimed states this is, so it has to read the dependency
310
+ // graph too: a dep added after promotion leaves the row at `ready` while
311
+ // the claim gate refuses it, and "waiting for a builder" would then be the
312
+ // one sentence on the page that is wrong.
313
+ const waitingOnDep = (t.dependencies || []).some((d) => d.status !== 'shipped');
314
+ const claimCaption = (t.status === 'ready' && waitingOnDep)
315
+ ? CLAIM_CAPTION.blocked
316
+ : (CLAIM_CAPTION[t.status] || 'one builder, one worktree');
308
317
  const beats = [
309
- ['Claim', CLAIM_CAPTION[t.status] || 'one builder, one worktree'],
318
+ ['Claim', claimCaption],
310
319
  ['Build', 'the code and the docs'],
311
320
  ['Grade', 'the panel reads the diff'],
312
321
  ['Ship', 'merged, then deployed'],
@@ -727,8 +736,22 @@
727
736
  // owed rebase, a module/touches conflict, …) is still possible and still
728
737
  // surfaces, because claimAction.claim() carries the same friendlyClaimError
729
738
  // switch the board uses, not a second guess at it here.
730
- if (t.status === 'ready') {
739
+ //
740
+ // task 1004487 — the status is NOT the whole answer, and the paragraph
741
+ // above assumed it was. A dependency added after this task was promoted
742
+ // leaves the row at `ready` and never at `blocked`: ADR 0015 rules that the
743
+ // status stays put and the READERS change, which is what core_264 did for
744
+ // the claimable feed. So the button also asks the dependency question the
745
+ // feed asks, from the `dependencies[]` the record already carries (an edge
746
+ // is met iff its target shipped — the DEPS_NOT_SHIPPED rule verbatim), and
747
+ // when the answer is no it says so in words rather than offering a control
748
+ // that cannot work, exactly as the retire refusals below do.
749
+ const unmetDeps = deps.filter((d) => d.status !== 'shipped');
750
+ if (t.status === 'ready' && unmetDeps.length === 0) {
731
751
  lifecycle = `<button class="td-btn td-btn--primary" type="button" id="claim-btn">Claim it</button>`;
752
+ } else if (t.status === 'ready') {
753
+ const names = unmetDeps.map((d) => `${d.id}`).join(', ');
754
+ lifecycle = `<p class="rec-actnote">This task is waiting on ${unmetDeps.length === 1 ? 'task' : 'tasks'} ${escapeHtml(names)}. It cannot be claimed until ${unmetDeps.length === 1 ? 'that one ships' : 'those ship'} — see Blocked by above.</p>`;
732
755
  } else if (t.status === 'confirmed' && (isSupervisor || ownsLastClaim)) {
733
756
  lifecycle = `<button class="td-btn td-btn--primary" type="button" id="ship-btn">Ship it</button>`;
734
757
  } else if (t.status === 'completed') {
@@ -0,0 +1,182 @@
1
+ 'use strict';
2
+
3
+ // modules/lifecycle/goal-move.js — move a goal, with everything it holds, to
4
+ // another version (task 1004485).
5
+ //
6
+ // WHY. A goal's version was write-once. PATCH /goals/:id edits prose, never
7
+ // version_id, and PATCH /tasks/:id refuses `goal_version_mismatch` — so the only
8
+ // way to re-home a goal that did not make its version (the 2026-10-01 case:
9
+ // goals 1000122 and 1000123 deferred from BONGOS-V2 to BONGOS-V3) was to recreate
10
+ // it and its tasks under new ids, or a hand-written prod UPDATE.
11
+ //
12
+ // THE GOAL MOVES WHOLE. The goal row, every done-when criterion parented to it,
13
+ // and every task in it — terminal ones included — change version in ONE
14
+ // transaction. Two half-moves are the failure shapes this exists to avoid:
15
+ // • tasks left behind would sit in a goal of another version, which PATCH
16
+ // /tasks/:id refuses for exactly that reason (a task joins a goal in its own
17
+ // version only);
18
+ // • a task moved by goal_id but not version_id is the carryBugsForward lesson
19
+ // (db-goals.js, BV1.R18c): `version_progress` joins on tasks.version_id, so a
20
+ // half-moved task keeps counting against the version it left.
21
+ //
22
+ // WHAT IS REFUSED, and why:
23
+ // • a maintenance goal — only carryBugsForward moves those, at version close;
24
+ // • a goal that is not open — achieved/archived goals are history;
25
+ // • a goal on a SHIPPED version — moving it would rewrite closed scope-truth;
26
+ // • the same version — a no-op dressed as a write;
27
+ // • a target that would refuse a new goal — the SAME gate POST /goals uses
28
+ // (authorizeGoalCreate), so "may this version take this goal" has one answer.
29
+ // A building target therefore needs the Archon `admit_reason` override, and
30
+ // the admission is recorded exactly as a create's is;
31
+ // • a criterion link that crosses the goal's edge (a task inside linked to a
32
+ // criterion outside, or the reverse) — after the move it would join two
33
+ // versions, and /status would count it against the wrong one. The caller
34
+ // unlinks it first; the refusal names every pair.
35
+ // An ACTIVE CLAIM is not a refusal: re-pointing the version takes no work from
36
+ // anyone (the claim references the task, untouched) — carryBugsForward's
37
+ // reasoning, for the same act.
38
+ //
39
+ // Lives in its own file because routes/goals.js sits at the fitness oversize
40
+ // line (1,500) and db-goals.js near it; a move is a cohesive slice, the
41
+ // goal-edits.js precedent.
42
+
43
+ const api = require('../../src/module-api');
44
+ const { executorFor } = require('./db-executor.js');
45
+ const { authorizeGoalCreate } = require('./routes/goal-route-authz.js');
46
+ const { pool } = api;
47
+
48
+ // authorizeGoalMove — PURE, FAIL-CLOSED. May this goal move to this version?
49
+ // The route gathers the facts; this decides; the route renders the verdict.
50
+ function authorizeGoalMove({ goal, source, target, admitReason = null }) {
51
+ if (!goal) {
52
+ return { ok: false, status: 404, body: { error: 'goal_not_found', message: 'No such goal.' } };
53
+ }
54
+ if (goal.is_maintenance) {
55
+ return { ok: false, status: 409, body: {
56
+ error: 'maintenance_goal',
57
+ message: `Goal #${goal.id} is its version's maintenance goal — it moves only with the version close (carry-forward), never by hand.`,
58
+ } };
59
+ }
60
+ if (goal.status !== 'open') {
61
+ return { ok: false, status: 409, body: {
62
+ error: 'goal_not_open', goal_status: goal.status,
63
+ message: `Goal #${goal.id} is '${goal.status}' — only an open goal can move to another version.`,
64
+ } };
65
+ }
66
+ if (source && source.status === 'shipped') {
67
+ return { ok: false, status: 409, body: {
68
+ error: 'source_version_shipped',
69
+ message: `Goal #${goal.id} belongs to '${source.id}', which has shipped — its goals are closed history.`,
70
+ } };
71
+ }
72
+ if (target && String(target.id) === String(goal.version_id)) {
73
+ return { ok: false, status: 409, body: {
74
+ error: 'same_version', message: `Goal #${goal.id} is already on '${target.id}'.`,
75
+ } };
76
+ }
77
+ const decision = authorizeGoalCreate({ version: target, override: admitReason !== null });
78
+ if (!decision.ok) return decision;
79
+ return { ok: true, overrideApplied: decision.overrideApplied };
80
+ }
81
+
82
+ // moveGoalToVersion — the write, in ONE transaction. Returns
83
+ // { fromVersionId, toVersionId, taskIds, criterionIds }. Throws a coded error the
84
+ // route translates:
85
+ // GOAL_NOT_FOUND, GOAL_CHANGED (status/version moved since the route checked),
86
+ // TARGET_CHANGED (the target's status moved since the route checked),
87
+ // CRITERION_LINKS_CROSS_GOAL (with .links), CRITERION_ID_COLLISION (a criterion
88
+ // slug the target version already uses — UNIQUE (version_id, criterion_id)).
89
+ async function moveGoalToVersion({ goalId, fromVersionId, toVersionId, targetStatus, admission = null }, deps = {}) {
90
+ const client = await executorFor(deps, pool).connect();
91
+ try {
92
+ await client.query('BEGIN');
93
+
94
+ const { rows: g } = await client.query(
95
+ `SELECT id, version_id, status FROM goals WHERE id = $1 FOR UPDATE`, [goalId]
96
+ );
97
+ if (!g[0]) throw Object.assign(new Error('goal not found'), { code: 'GOAL_NOT_FOUND' });
98
+ if (String(g[0].version_id) !== String(fromVersionId) || g[0].status !== 'open') {
99
+ throw Object.assign(new Error('goal changed'), { code: 'GOAL_CHANGED' });
100
+ }
101
+ // The target's status was what the gate judged; a close or promotion in
102
+ // between would let a goal into a version that now refuses it.
103
+ const { rows: v } = await client.query(
104
+ `SELECT status FROM versions WHERE id = $1 FOR SHARE`, [toVersionId]
105
+ );
106
+ if (!v[0] || v[0].status !== targetStatus) {
107
+ throw Object.assign(new Error('target changed'), { code: 'TARGET_CHANGED' });
108
+ }
109
+
110
+ // Exactly one side of the link inside this goal = the link crosses its edge.
111
+ const { rows: links } = await client.query(
112
+ `SELECT tc.task_id::text AS task_id, tc.criterion_id::text AS criterion_id
113
+ FROM task_criteria tc
114
+ JOIN tasks t ON t.id = tc.task_id
115
+ JOIN done_when_criteria c ON c.id = tc.criterion_id
116
+ WHERE (t.goal_id IS NOT DISTINCT FROM $1) <> (c.goal_id IS NOT DISTINCT FROM $1)
117
+ AND (t.goal_id = $1 OR c.goal_id = $1)
118
+ ORDER BY tc.task_id, tc.criterion_id
119
+ LIMIT 50`,
120
+ [goalId]
121
+ );
122
+ if (links.length > 0) {
123
+ throw Object.assign(new Error('criterion links cross the goal'), { code: 'CRITERION_LINKS_CROSS_GOAL', links });
124
+ }
125
+
126
+ // Appended to the end of the target's goal order rather than keeping a
127
+ // sort_order that meant something only among the old version's goals.
128
+ await client.query(
129
+ `UPDATE goals
130
+ SET version_id = $2,
131
+ sort_order = (SELECT COALESCE(MAX(sort_order), 0) + 10 FROM goals WHERE version_id = $2),
132
+ updated_at = now()
133
+ WHERE id = $1`,
134
+ [goalId, toVersionId]
135
+ );
136
+ let criterionIds;
137
+ try {
138
+ const { rows } = await client.query(
139
+ `UPDATE done_when_criteria SET version_id = $2 WHERE goal_id = $1 RETURNING id::text`,
140
+ [goalId, toVersionId]
141
+ );
142
+ criterionIds = rows.map((r) => r.id);
143
+ } catch (err) {
144
+ if (err && err.code === '23505') {
145
+ throw Object.assign(new Error('criterion id collision'), { code: 'CRITERION_ID_COLLISION' });
146
+ }
147
+ throw err;
148
+ }
149
+ const { rows: tasks } = await client.query(
150
+ `UPDATE tasks SET version_id = $2, updated_at = now() WHERE goal_id = $1 RETURNING id::text`,
151
+ [goalId, toVersionId]
152
+ );
153
+ // A building target was opened by the Archon override: recorded like a
154
+ // create's admission. goal_id is the table's key, so a goal admitted before
155
+ // carries its LATEST admission — the one that explains where it is now.
156
+ if (admission) {
157
+ await client.query(
158
+ `INSERT INTO lifecycle_goal_version_admissions (goal_id, version_id, admitted_by, reason)
159
+ VALUES ($1, $2, $3, $4)
160
+ ON CONFLICT (goal_id) DO UPDATE
161
+ SET version_id = EXCLUDED.version_id, admitted_by = EXCLUDED.admitted_by,
162
+ reason = EXCLUDED.reason, created_at = now()`,
163
+ [goalId, toVersionId, admission.admittedBy, admission.reason]
164
+ );
165
+ }
166
+
167
+ await client.query('COMMIT');
168
+ return {
169
+ fromVersionId: String(fromVersionId),
170
+ toVersionId: String(toVersionId),
171
+ taskIds: tasks.map((r) => r.id).sort((a, b) => Number(a) - Number(b)),
172
+ criterionIds: criterionIds.sort((a, b) => Number(a) - Number(b)),
173
+ };
174
+ } catch (err) {
175
+ await client.query('ROLLBACK').catch(() => {});
176
+ throw err;
177
+ } finally {
178
+ if (typeof client.release === 'function') client.release();
179
+ }
180
+ }
181
+
182
+ module.exports = { authorizeGoalMove, moveGoalToVersion };
@@ -19,10 +19,11 @@
19
19
  // THE BUCKETS ARE THE CLIENT'S, VERBATIM. goals-lib.js `readinessOf` is the
20
20
  // definition of record and this SQL mirrors it exactly — done (shipped |
21
21
  // confirmed | completed) · cut (abandoned | dropped, excluded from the total the
22
- // way migration 017 keeps them out of version_progress) · active · ready ·
23
- // blocked (backlog with >=1 unshipped dependency) · backlog. A divergence here
24
- // would show a person different numbers depending on which surface they opened,
25
- // so tests/goal_sort_staleness.mjs pins the two against each other.
22
+ // way migration 017 keeps them out of version_progress) · active · blocked
23
+ // (unclaimed with >=1 unshipped dependency, whatever the status column says —
24
+ // task 1004487) · ready · backlog. A divergence here would show a person
25
+ // different numbers depending on which surface they opened, so
26
+ // tests/goal_sort_staleness.mjs pins the two against each other.
26
27
  //
27
28
  // LAST ACTIVITY is `max(updated_at, shipped_at)` — the same "any task activity
28
29
  // counts" rule the client uses (task 1002965, the owner's call), now expressed
@@ -43,8 +44,8 @@ const { DONE_STATUSES, CUT_STATUSES } = require('./task-status');
43
44
  // Returns [{ goal_id, done, active, ready, blocked, backlog, cut, total,
44
45
  // last_activity_at, disciplines[], next_ready_id, next_ready_title }].
45
46
  //
46
- // `blocked` needs the dependency edges, so the CTE resolves each backlog task's
47
- // unshipped-dependency count once rather than per row. deps.pool honors the #538
47
+ // `blocked` needs the dependency edges, so the CTE resolves each task's
48
+ // unshipped-dependency flag once rather than per row. deps.pool honors the #538
48
49
  // test seam (NODE_ENV==='test' only), like every other helper in this module.
49
50
  // VISIBILITY. A rollup is a COUNT, and counts on a private goal are not the
50
51
  // secret — its prose is (ADR 0112's leak rule: a non-member must not learn a
@@ -78,8 +79,13 @@ async function goalRollups({ versionId = null, viewerId = null, viewerIsArchon =
78
79
  WHEN s.status = ANY($2::text[]) THEN 'done'
79
80
  WHEN s.status = ANY($3::text[]) THEN 'cut'
80
81
  WHEN s.status = 'active' THEN 'active'
82
+ -- task 1004487 — ahead of the 'ready' arm, and no longer
83
+ -- backlog-only: a dependency added after promotion leaves the
84
+ -- row at 'ready' (ADR 0015 demotes the readers, never the
85
+ -- status), so counting it ready advertises work POST /claims
86
+ -- refuses — and next_ready below reads this very bucket.
87
+ WHEN s.has_unshipped_dep THEN 'blocked'
81
88
  WHEN s.status = 'ready' THEN 'ready'
82
- WHEN s.status = 'backlog' AND s.has_unshipped_dep THEN 'blocked'
83
89
  ELSE 'backlog'
84
90
  END AS bucket
85
91
  FROM scoped s
@@ -16,6 +16,7 @@
16
16
  "done-when",
17
17
  "criterion-uat",
18
18
  "goals",
19
+ "goal-move",
19
20
  "dependencies",
20
21
  "gate-approvals",
21
22
  "analytics",