@telora/daemon 0.21.83 → 0.21.134

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.
Files changed (267) hide show
  1. package/build-info.json +10 -3
  2. package/dist/audit/audit-hooks.d.ts +106 -0
  3. package/dist/audit/audit-hooks.d.ts.map +1 -1
  4. package/dist/audit/audit-hooks.js +149 -17
  5. package/dist/audit/audit-hooks.js.map +1 -1
  6. package/dist/ci/ci-sensor-loop.d.ts +6 -0
  7. package/dist/ci/ci-sensor-loop.d.ts.map +1 -1
  8. package/dist/ci/ci-sensor-loop.js +50 -3
  9. package/dist/ci/ci-sensor-loop.js.map +1 -1
  10. package/dist/ci/ci-status.d.ts +63 -11
  11. package/dist/ci/ci-status.d.ts.map +1 -1
  12. package/dist/ci/ci-status.js +94 -27
  13. package/dist/ci/ci-status.js.map +1 -1
  14. package/dist/completion/coverage-drift-state.d.ts +29 -2
  15. package/dist/completion/coverage-drift-state.d.ts.map +1 -1
  16. package/dist/completion/coverage-drift-state.js +42 -2
  17. package/dist/completion/coverage-drift-state.js.map +1 -1
  18. package/dist/completion/coverage-stabilized.d.ts +58 -0
  19. package/dist/completion/coverage-stabilized.d.ts.map +1 -0
  20. package/dist/completion/coverage-stabilized.js +84 -0
  21. package/dist/completion/coverage-stabilized.js.map +1 -0
  22. package/dist/completion/delivery-advancement-guard.d.ts +21 -0
  23. package/dist/completion/delivery-advancement-guard.d.ts.map +1 -1
  24. package/dist/completion/delivery-advancement-guard.js +31 -3
  25. package/dist/completion/delivery-advancement-guard.js.map +1 -1
  26. package/dist/completion/event-escalations.d.ts +15 -0
  27. package/dist/completion/event-escalations.d.ts.map +1 -1
  28. package/dist/completion/event-escalations.js +42 -4
  29. package/dist/completion/event-escalations.js.map +1 -1
  30. package/dist/completion/event-phases.d.ts.map +1 -1
  31. package/dist/completion/event-phases.js +10 -5
  32. package/dist/completion/event-phases.js.map +1 -1
  33. package/dist/completion/machine-remediable-demotion.d.ts +74 -0
  34. package/dist/completion/machine-remediable-demotion.d.ts.map +1 -0
  35. package/dist/completion/machine-remediable-demotion.js +147 -0
  36. package/dist/completion/machine-remediable-demotion.js.map +1 -0
  37. package/dist/completion/review-exit-escalations.d.ts +36 -1
  38. package/dist/completion/review-exit-escalations.d.ts.map +1 -1
  39. package/dist/completion/review-exit-escalations.js +81 -0
  40. package/dist/completion/review-exit-escalations.js.map +1 -1
  41. package/dist/completion/review-exit-phase.d.ts +4 -2
  42. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  43. package/dist/completion/review-exit-phase.js +49 -54
  44. package/dist/completion/review-exit-phase.js.map +1 -1
  45. package/dist/completion/settle-injection-sweep.d.ts +55 -0
  46. package/dist/completion/settle-injection-sweep.d.ts.map +1 -0
  47. package/dist/completion/settle-injection-sweep.js +88 -0
  48. package/dist/completion/settle-injection-sweep.js.map +1 -0
  49. package/dist/completion/state-cascade.d.ts.map +1 -1
  50. package/dist/completion/state-cascade.js +41 -0
  51. package/dist/completion/state-cascade.js.map +1 -1
  52. package/dist/completion/status-advance-phase.d.ts.map +1 -1
  53. package/dist/completion/status-advance-phase.js +28 -30
  54. package/dist/completion/status-advance-phase.js.map +1 -1
  55. package/dist/completion/team-lifecycle.d.ts +42 -0
  56. package/dist/completion/team-lifecycle.d.ts.map +1 -1
  57. package/dist/completion/team-lifecycle.js +79 -2
  58. package/dist/completion/team-lifecycle.js.map +1 -1
  59. package/dist/delivery/delivery-lifecycle.d.ts +19 -33
  60. package/dist/delivery/delivery-lifecycle.d.ts.map +1 -1
  61. package/dist/delivery/delivery-lifecycle.js +26 -160
  62. package/dist/delivery/delivery-lifecycle.js.map +1 -1
  63. package/dist/delivery/delivery-merge-ops.d.ts +46 -0
  64. package/dist/delivery/delivery-merge-ops.d.ts.map +1 -0
  65. package/dist/delivery/delivery-merge-ops.js +154 -0
  66. package/dist/delivery/delivery-merge-ops.js.map +1 -0
  67. package/dist/delivery/delivery-reconciler-apply.d.ts +108 -66
  68. package/dist/delivery/delivery-reconciler-apply.d.ts.map +1 -1
  69. package/dist/delivery/delivery-reconciler-apply.js +87 -61
  70. package/dist/delivery/delivery-reconciler-apply.js.map +1 -1
  71. package/dist/delivery/delivery-reconciler.d.ts +7 -2
  72. package/dist/delivery/delivery-reconciler.d.ts.map +1 -1
  73. package/dist/delivery/delivery-reconciler.js +34 -5
  74. package/dist/delivery/delivery-reconciler.js.map +1 -1
  75. package/dist/delivery/external-hold-surface.d.ts +32 -0
  76. package/dist/delivery/external-hold-surface.d.ts.map +1 -0
  77. package/dist/delivery/external-hold-surface.js +67 -0
  78. package/dist/delivery/external-hold-surface.js.map +1 -0
  79. package/dist/delivery/external-hold.d.ts +38 -0
  80. package/dist/delivery/external-hold.d.ts.map +1 -1
  81. package/dist/delivery/external-hold.js +69 -6
  82. package/dist/delivery/external-hold.js.map +1 -1
  83. package/dist/delivery/operator-hold.d.ts +1 -2
  84. package/dist/delivery/operator-hold.d.ts.map +1 -1
  85. package/dist/delivery/operator-hold.js +1 -2
  86. package/dist/delivery/operator-hold.js.map +1 -1
  87. package/dist/delivery/paused-revival.d.ts +140 -0
  88. package/dist/delivery/paused-revival.d.ts.map +1 -0
  89. package/dist/delivery/paused-revival.js +229 -0
  90. package/dist/delivery/paused-revival.js.map +1 -0
  91. package/dist/delivery/wait-reachability.d.ts +149 -0
  92. package/dist/delivery/wait-reachability.d.ts.map +1 -0
  93. package/dist/delivery/wait-reachability.js +193 -0
  94. package/dist/delivery/wait-reachability.js.map +1 -0
  95. package/dist/directive/close-loop-dispatcher.d.ts +16 -263
  96. package/dist/directive/close-loop-dispatcher.d.ts.map +1 -1
  97. package/dist/directive/close-loop-dispatcher.js +21 -259
  98. package/dist/directive/close-loop-dispatcher.js.map +1 -1
  99. package/dist/directive/directive-dispatch.d.ts +19 -0
  100. package/dist/directive/directive-dispatch.d.ts.map +1 -1
  101. package/dist/directive/directive-dispatch.js +18 -0
  102. package/dist/directive/directive-dispatch.js.map +1 -1
  103. package/dist/directive/directive-executor.d.ts +1 -3
  104. package/dist/directive/directive-executor.d.ts.map +1 -1
  105. package/dist/directive/directive-executor.js +23 -21
  106. package/dist/directive/directive-executor.js.map +1 -1
  107. package/dist/directive/phase-sync.d.ts +28 -15
  108. package/dist/directive/phase-sync.d.ts.map +1 -1
  109. package/dist/directive/phase-sync.js +28 -36
  110. package/dist/directive/phase-sync.js.map +1 -1
  111. package/dist/directive/resolver-dispatch.d.ts +19 -92
  112. package/dist/directive/resolver-dispatch.d.ts.map +1 -1
  113. package/dist/directive/resolver-dispatch.js +19 -65
  114. package/dist/directive/resolver-dispatch.js.map +1 -1
  115. package/dist/focus/focus-engine.d.ts.map +1 -1
  116. package/dist/focus/focus-engine.js +26 -0
  117. package/dist/focus/focus-engine.js.map +1 -1
  118. package/dist/focus/focus-executor.d.ts.map +1 -1
  119. package/dist/focus/focus-executor.js +290 -256
  120. package/dist/focus/focus-executor.js.map +1 -1
  121. package/dist/focus/focus-loop-registry.d.ts.map +1 -1
  122. package/dist/focus/focus-loop-registry.js +43 -1
  123. package/dist/focus/focus-loop-registry.js.map +1 -1
  124. package/dist/focus/focus-respawn-guard.d.ts.map +1 -1
  125. package/dist/focus/focus-respawn-guard.js +11 -2
  126. package/dist/focus/focus-respawn-guard.js.map +1 -1
  127. package/dist/focus/focus-team-state.d.ts +11 -0
  128. package/dist/focus/focus-team-state.d.ts.map +1 -1
  129. package/dist/focus/focus-team-state.js +17 -0
  130. package/dist/focus/focus-team-state.js.map +1 -1
  131. package/dist/focus/listener-auto-advance.d.ts +13 -1
  132. package/dist/focus/listener-auto-advance.d.ts.map +1 -1
  133. package/dist/focus/listener-auto-advance.js +112 -76
  134. package/dist/focus/listener-auto-advance.js.map +1 -1
  135. package/dist/focus/listener.d.ts.map +1 -1
  136. package/dist/focus/listener.js +141 -75
  137. package/dist/focus/listener.js.map +1 -1
  138. package/dist/focus/tick-deadline.d.ts +55 -0
  139. package/dist/focus/tick-deadline.d.ts.map +1 -0
  140. package/dist/focus/tick-deadline.js +91 -0
  141. package/dist/focus/tick-deadline.js.map +1 -0
  142. package/dist/focus-stage-lifecycle/index.d.ts +18 -25
  143. package/dist/focus-stage-lifecycle/index.d.ts.map +1 -1
  144. package/dist/focus-stage-lifecycle/index.js +26 -38
  145. package/dist/focus-stage-lifecycle/index.js.map +1 -1
  146. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.d.ts +52 -0
  147. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.d.ts.map +1 -0
  148. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.js +94 -0
  149. package/dist/focus-stage-lifecycle/wedge-cause-arbiter.js.map +1 -0
  150. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts +18 -1
  151. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts.map +1 -1
  152. package/dist/focus-stage-lifecycle/wedge-watchdog.js +57 -7
  153. package/dist/focus-stage-lifecycle/wedge-watchdog.js.map +1 -1
  154. package/dist/git/merge.d.ts.map +1 -1
  155. package/dist/git/merge.js +12 -3
  156. package/dist/git/merge.js.map +1 -1
  157. package/dist/guard/condition-evaluators.d.ts +9 -0
  158. package/dist/guard/condition-evaluators.d.ts.map +1 -1
  159. package/dist/guard/condition-evaluators.js +15 -0
  160. package/dist/guard/condition-evaluators.js.map +1 -1
  161. package/dist/guard/guard-evaluator.d.ts +17 -1
  162. package/dist/guard/guard-evaluator.d.ts.map +1 -1
  163. package/dist/guard/guard-evaluator.js +61 -2
  164. package/dist/guard/guard-evaluator.js.map +1 -1
  165. package/dist/guard/trigger-executor.d.ts +30 -18
  166. package/dist/guard/trigger-executor.d.ts.map +1 -1
  167. package/dist/guard/trigger-executor.js +55 -31
  168. package/dist/guard/trigger-executor.js.map +1 -1
  169. package/dist/prompt/team-prompt-base.d.ts +1 -1
  170. package/dist/prompt/team-prompt-base.d.ts.map +1 -1
  171. package/dist/prompt/team-prompt-base.js +1 -1
  172. package/dist/prompt/team-prompt-base.js.map +1 -1
  173. package/dist/prompt-sections/persona-sections.d.ts +0 -32
  174. package/dist/prompt-sections/persona-sections.d.ts.map +1 -1
  175. package/dist/prompt-sections/persona-sections.js +19 -88
  176. package/dist/prompt-sections/persona-sections.js.map +1 -1
  177. package/dist/queries/deliveries.d.ts +33 -0
  178. package/dist/queries/deliveries.d.ts.map +1 -1
  179. package/dist/queries/deliveries.js +7 -0
  180. package/dist/queries/deliveries.js.map +1 -1
  181. package/dist/queries/focuses.d.ts +46 -0
  182. package/dist/queries/focuses.d.ts.map +1 -1
  183. package/dist/queries/focuses.js +60 -0
  184. package/dist/queries/focuses.js.map +1 -1
  185. package/dist/queries/guards.d.ts +10 -0
  186. package/dist/queries/guards.d.ts.map +1 -1
  187. package/dist/queries/guards.js +14 -1
  188. package/dist/queries/guards.js.map +1 -1
  189. package/dist/queries/issues.d.ts +65 -1
  190. package/dist/queries/issues.d.ts.map +1 -1
  191. package/dist/queries/issues.js +135 -2
  192. package/dist/queries/issues.js.map +1 -1
  193. package/dist/queries/schemas.d.ts +53 -0
  194. package/dist/queries/schemas.d.ts.map +1 -1
  195. package/dist/queries/schemas.js +38 -0
  196. package/dist/queries/schemas.js.map +1 -1
  197. package/dist/queries/verification.d.ts +5 -6
  198. package/dist/queries/verification.d.ts.map +1 -1
  199. package/dist/queries/verification.js +5 -6
  200. package/dist/queries/verification.js.map +1 -1
  201. package/dist/resolvers/loop-persona.d.ts +1 -11
  202. package/dist/resolvers/loop-persona.d.ts.map +1 -1
  203. package/dist/resolvers/loop-persona.js +2 -16
  204. package/dist/resolvers/loop-persona.js.map +1 -1
  205. package/dist/shell/heartbeat.d.ts +7 -0
  206. package/dist/shell/heartbeat.d.ts.map +1 -1
  207. package/dist/shell/heartbeat.js +1 -1
  208. package/dist/shell/heartbeat.js.map +1 -1
  209. package/dist/shell/unified-shell-provisioning.d.ts +40 -0
  210. package/dist/shell/unified-shell-provisioning.d.ts.map +1 -1
  211. package/dist/shell/unified-shell-provisioning.js +72 -0
  212. package/dist/shell/unified-shell-provisioning.js.map +1 -1
  213. package/dist/shell/unified-shell.d.ts.map +1 -1
  214. package/dist/shell/unified-shell.js +18 -1
  215. package/dist/shell/unified-shell.js.map +1 -1
  216. package/dist/spawn/helpers.d.ts.map +1 -1
  217. package/dist/spawn/helpers.js +6 -1
  218. package/dist/spawn/helpers.js.map +1 -1
  219. package/dist/spawn/process-spawn.d.ts.map +1 -1
  220. package/dist/spawn/process-spawn.js +7 -0
  221. package/dist/spawn/process-spawn.js.map +1 -1
  222. package/dist/spawn/respawn-cycle-escalation.d.ts +41 -0
  223. package/dist/spawn/respawn-cycle-escalation.d.ts.map +1 -0
  224. package/dist/spawn/respawn-cycle-escalation.js +83 -0
  225. package/dist/spawn/respawn-cycle-escalation.js.map +1 -0
  226. package/dist/spawn/spawn-cooldown.d.ts +119 -2
  227. package/dist/spawn/spawn-cooldown.d.ts.map +1 -1
  228. package/dist/spawn/spawn-cooldown.js +120 -10
  229. package/dist/spawn/spawn-cooldown.js.map +1 -1
  230. package/dist/spawn/spawn-decision.d.ts.map +1 -1
  231. package/dist/spawn/spawn-decision.js +33 -1
  232. package/dist/spawn/spawn-decision.js.map +1 -1
  233. package/dist/testing/fixtures/index.d.ts +64 -0
  234. package/dist/testing/fixtures/index.d.ts.map +1 -0
  235. package/dist/testing/fixtures/index.js +220 -0
  236. package/dist/testing/fixtures/index.js.map +1 -0
  237. package/dist/update/build-info.d.ts +1 -0
  238. package/dist/update/build-info.d.ts.map +1 -1
  239. package/dist/update/build-info.js +1 -1
  240. package/dist/update/build-info.js.map +1 -1
  241. package/dist/verify/stall-detector-config.d.ts +8 -0
  242. package/dist/verify/stall-detector-config.d.ts.map +1 -1
  243. package/dist/verify/stall-detector-config.js +40 -0
  244. package/dist/verify/stall-detector-config.js.map +1 -1
  245. package/dist/verify/stall-detectors.d.ts +57 -14
  246. package/dist/verify/stall-detectors.d.ts.map +1 -1
  247. package/dist/verify/stall-detectors.js +72 -55
  248. package/dist/verify/stall-detectors.js.map +1 -1
  249. package/dist/verify/verification-feedback.d.ts +3 -4
  250. package/dist/verify/verification-feedback.d.ts.map +1 -1
  251. package/dist/verify/verification-feedback.js +3 -12
  252. package/dist/verify/verification-feedback.js.map +1 -1
  253. package/dist/workflow/gate-failure-policy.d.ts +48 -0
  254. package/dist/workflow/gate-failure-policy.d.ts.map +1 -0
  255. package/dist/workflow/gate-failure-policy.js +57 -0
  256. package/dist/workflow/gate-failure-policy.js.map +1 -0
  257. package/dist/workflow/pipeline-config.d.ts +2 -2
  258. package/dist/workflow/pipeline-config.js +2 -2
  259. package/package.json +3 -3
  260. package/dist/directive/close-loop-stage.d.ts +0 -50
  261. package/dist/directive/close-loop-stage.d.ts.map +0 -1
  262. package/dist/directive/close-loop-stage.js +0 -210
  263. package/dist/directive/close-loop-stage.js.map +0 -1
  264. package/dist/focus-stage-lifecycle/review-convergence.d.ts +0 -133
  265. package/dist/focus-stage-lifecycle/review-convergence.d.ts.map +0 -1
  266. package/dist/focus-stage-lifecycle/review-convergence.js +0 -237
  267. package/dist/focus-stage-lifecycle/review-convergence.js.map +0 -1
@@ -25,6 +25,7 @@
25
25
  * @module ci-status
26
26
  */
27
27
  import type { DaemonConfig } from '../types/index.js';
28
+ import { type WaitReachability } from '../delivery/wait-reachability.js';
28
29
  /**
29
30
  * Normalized CI run status for a SHA.
30
31
  * - green -- a CI run for this SHA completed successfully.
@@ -43,6 +44,19 @@ export interface CiMergeGateDecision {
43
44
  status: CiRunStatus;
44
45
  /** Human-readable reason (logged; surfaced in the session exit suffix). */
45
46
  reason: string;
47
+ /**
48
+ * Reachability of the HELD wait: can the awaited CI condition still fire?
49
+ * Populated only for a hold (proceed=false) that a caller has classified with
50
+ * the merged sha via {@link classifyCiMergeWait}; absent on the pure
51
+ * status-only decision from {@link resolveCiMergeGate}. `reachable:false` marks
52
+ * the incident's trap -- a settled non-green result on an UNCHANGED sha that
53
+ * cannot self-clear without a NEW commit, so holding for it is a stall, not
54
+ * patience. `reachable:true` marks a run still in flight (the CI runner can
55
+ * still finish it).
56
+ */
57
+ reachable?: boolean;
58
+ /** The reachability reason (from the wait-reachability classifier), when set. */
59
+ reachabilityReason?: string;
46
60
  }
47
61
  /**
48
62
  * Pure merge-gate decision: should integration->main proceed given the merged
@@ -62,28 +76,66 @@ export interface CiMergeGateDecision {
62
76
  * a non-green (or absent) status for the merged SHA.
63
77
  */
64
78
  export declare function resolveCiMergeGate(status: CiRunStatus): CiMergeGateDecision;
79
+ /**
80
+ * Classify whether a HELD CI merge wait can still fire, given the merged SHA.
81
+ * This is the reachability distinction the plain status-only gate cannot make:
82
+ * the merge gate correctly HOLDS on pending / red / unknown, but only some of
83
+ * those waits can still be satisfied without new input.
84
+ *
85
+ * - pending -> REACHABLE: a run is in flight; the CI runner (another actor)
86
+ * can still finish it and turn the SHA green.
87
+ * - red -> UNREACHABLE: a settled failure recorded for THIS sha. It never
88
+ * re-evaluates for the same commit (refs/ci-status/<sha> is immutable per
89
+ * sha), so waiting for it to "turn green" cannot succeed without a NEW
90
+ * commit. This is the 2026-08-17 "CI-red result auto-resolving" trap: a
91
+ * stall that reads as patience.
92
+ * - unknown -> REACHABLE (conservative): no status could be determined, so a
93
+ * run could still appear. We do NOT declare an unknown wait dead -- only a
94
+ * recorded non-green RESULT is a static fact.
95
+ * - green -> not held; classified REACHABLE for totality (never reached on a
96
+ * hold path).
97
+ *
98
+ * Pure. Reuses the shared wait-reachability classifier so the CI hold speaks the
99
+ * same vocabulary as every other wait.
100
+ */
101
+ export declare function classifyCiMergeWait(status: CiRunStatus, sha: string): WaitReachability;
65
102
  /**
66
103
  * Classify a git-native CI status from the set of ci-status ref names published
67
104
  * for a SHA. The STATE is encoded in the ref NAME (immutable per-SHA), so no
68
105
  * object content read is needed. Pure -- the caller supplies the ref names from a
69
106
  * scoped `git ls-remote` / fetch.
70
107
  *
71
- * MOST-RECENT-RUN-WINS. The publisher encodes run order in the ref PATH
72
- * (`refs/ci-status/<sha>/<runNumber>/<state>`), so a later GREEN run's ref
73
- * outranks an earlier FAILURE ref for the same sha. This fixes the "a failure
74
- * ref outranks a success ref, forever" trap: ref names carried no order, so a
75
- * sha that once published a failure (an infra blip, or an evicted run) stayed
76
- * red permanently even after a real green run of the same sha. The order comes
77
- * from the workflow, never inferred by this reader.
108
+ * COMPLETED-GREEN-WINS across runs. The publisher encodes run order in the ref
109
+ * PATH (`refs/ci-status/<sha>/<runNumber>/<state>`) and publishes `success` ONLY
110
+ * when the whole suite (ci AND e2e) completed green. A completed green run is
111
+ * therefore unforgeable proof the SHA's tree is sound: a genuinely broken tree
112
+ * can never publish green, and a run destroyed mid-suite can never reach a green
113
+ * e2e result. So among the distinct runs of one sha, ANY completed green run
114
+ * outranks a red run of the same sha -- the red is the environmental casualty
115
+ * (an evicted run, or one whose Postgres another overlapping run force-removed
116
+ * mid-suite), not the tree.
117
+ *
118
+ * This REPLACES the earlier most-recent-run-wins rule, which the 2026-08-18
119
+ * shared-runner incident disproved: run 3838's live database was force-removed
120
+ * by an overlapping run 3837, so 3838 published a failure -- and because the
121
+ * DESTROYED run carried the HIGHER run number, "most recent wins" let its red
122
+ * pin a sound sha red. Run-id order did not even match start order there (3838
123
+ * started before 3837), so "most recent run" was never reliably most-recent.
124
+ * Completed-green does not depend on run-number order at all.
78
125
  *
79
- * FAIL-CLOSED throughout: an empty set / unrecognized suffix -> 'unknown'
80
- * (holds); among the highest run number, RED wins on a tie (the rare same-run
81
- * re-run that published both states resolves to red, never a false green).
126
+ * FAIL-CLOSED throughout: an empty set / unrecognized suffix / only-pending ->
127
+ * 'unknown'/'pending' (holds -- absent or ambiguous-without-a-completed-green
128
+ * evidence never passes). Within a SINGLE run, RED still wins a green/red tie
129
+ * (the rare same-run re-run that published both states resolves that run to red,
130
+ * never a false green); a sha with NO green run anywhere resolves to red, so a
131
+ * truly broken tree is never masked. The order/verdict comes from the workflow,
132
+ * never inferred by this reader.
82
133
  *
83
134
  * LEGACY refs (`refs/ci-status/<sha>/<state>`, no run number) carry no order; if
84
135
  * ONLY legacy refs exist we fall back to the original fail-closed precedence
85
136
  * (failure outranks success outranks pending), preserving prior behavior for
86
- * shas published before the run-ordered scheme deployed.
137
+ * shas published before the run-ordered scheme deployed. Any ordered ref takes
138
+ * precedence over legacy refs for the same sha.
87
139
  */
88
140
  export declare function classifyGitNativeCiStatus(refNames: readonly string[]): CiRunStatus;
89
141
  /** Parameters passed to a CI status reader. */
@@ -1 +1 @@
1
- {"version":3,"file":"ci-status.d.ts","sourceRoot":"","sources":["../../src/ci/ci-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAElE,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,MAAM,EAAE,WAAW,CAAC;IACpB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,mBAAmB,CAY3E;AA8BD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAuClF;AAED,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAElF;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,KAAK,MAAM,EAAE,CAAC;AAEhF;;;;GAIG;AACH,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,oBAAoB,EAAE,OAAO,GAAG,cAAc,CAIvG;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,iBAAiB,GAAG,cAAc,CAWvF"}
1
+ {"version":3,"file":"ci-status.d.ts","sourceRoot":"","sources":["../../src/ci/ci-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,kCAAkC,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAElE,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,MAAM,EAAE,WAAW,CAAC;IACpB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,mBAAmB,CAY3E;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAUtF;AA8BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAuDlF;AAED,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+EAA+E;IAC/E,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAElF;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,KAAK,MAAM,EAAE,CAAC;AAEhF;;;;GAIG;AACH,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,oBAAoB,EAAE,OAAO,GAAG,cAAc,CAIvG;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,iBAAiB,GAAG,cAAc,CAWvF"}
@@ -24,6 +24,7 @@
24
24
  *
25
25
  * @module ci-status
26
26
  */
27
+ import { classifyWaitReachability, ciRunPendingWait, recordedCiResultWait, } from '../delivery/wait-reachability.js';
27
28
  /**
28
29
  * Pure merge-gate decision: should integration->main proceed given the merged
29
30
  * SHA's CI status? FAIL-CLOSED (Jeremy decision 2026-07-05): only an explicit
@@ -54,6 +55,39 @@ export function resolveCiMergeGate(status) {
54
55
  return { proceed: false, status: 'unknown', reason: 'CI status unavailable for the merged SHA -- holding (fail-closed: unknown != green)' };
55
56
  }
56
57
  }
58
+ /**
59
+ * Classify whether a HELD CI merge wait can still fire, given the merged SHA.
60
+ * This is the reachability distinction the plain status-only gate cannot make:
61
+ * the merge gate correctly HOLDS on pending / red / unknown, but only some of
62
+ * those waits can still be satisfied without new input.
63
+ *
64
+ * - pending -> REACHABLE: a run is in flight; the CI runner (another actor)
65
+ * can still finish it and turn the SHA green.
66
+ * - red -> UNREACHABLE: a settled failure recorded for THIS sha. It never
67
+ * re-evaluates for the same commit (refs/ci-status/<sha> is immutable per
68
+ * sha), so waiting for it to "turn green" cannot succeed without a NEW
69
+ * commit. This is the 2026-08-17 "CI-red result auto-resolving" trap: a
70
+ * stall that reads as patience.
71
+ * - unknown -> REACHABLE (conservative): no status could be determined, so a
72
+ * run could still appear. We do NOT declare an unknown wait dead -- only a
73
+ * recorded non-green RESULT is a static fact.
74
+ * - green -> not held; classified REACHABLE for totality (never reached on a
75
+ * hold path).
76
+ *
77
+ * Pure. Reuses the shared wait-reachability classifier so the CI hold speaks the
78
+ * same vocabulary as every other wait.
79
+ */
80
+ export function classifyCiMergeWait(status, sha) {
81
+ switch (status) {
82
+ case 'red':
83
+ return classifyWaitReachability(recordedCiResultWait(sha, 'red'));
84
+ case 'pending':
85
+ case 'unknown':
86
+ case 'green':
87
+ default:
88
+ return classifyWaitReachability(ciRunPendingWait(sha));
89
+ }
90
+ }
57
91
  /** The recognized state a ci-status ref suffix encodes, or null if unrecognized. */
58
92
  function ciStateFromRefSuffix(ref) {
59
93
  if (ref.endsWith('/failure') || ref.endsWith('/error') || ref.endsWith('/red'))
@@ -89,26 +123,42 @@ function ciRunOrderFromRef(ref) {
89
123
  * object content read is needed. Pure -- the caller supplies the ref names from a
90
124
  * scoped `git ls-remote` / fetch.
91
125
  *
92
- * MOST-RECENT-RUN-WINS. The publisher encodes run order in the ref PATH
93
- * (`refs/ci-status/<sha>/<runNumber>/<state>`), so a later GREEN run's ref
94
- * outranks an earlier FAILURE ref for the same sha. This fixes the "a failure
95
- * ref outranks a success ref, forever" trap: ref names carried no order, so a
96
- * sha that once published a failure (an infra blip, or an evicted run) stayed
97
- * red permanently even after a real green run of the same sha. The order comes
98
- * from the workflow, never inferred by this reader.
126
+ * COMPLETED-GREEN-WINS across runs. The publisher encodes run order in the ref
127
+ * PATH (`refs/ci-status/<sha>/<runNumber>/<state>`) and publishes `success` ONLY
128
+ * when the whole suite (ci AND e2e) completed green. A completed green run is
129
+ * therefore unforgeable proof the SHA's tree is sound: a genuinely broken tree
130
+ * can never publish green, and a run destroyed mid-suite can never reach a green
131
+ * e2e result. So among the distinct runs of one sha, ANY completed green run
132
+ * outranks a red run of the same sha -- the red is the environmental casualty
133
+ * (an evicted run, or one whose Postgres another overlapping run force-removed
134
+ * mid-suite), not the tree.
99
135
  *
100
- * FAIL-CLOSED throughout: an empty set / unrecognized suffix -> 'unknown'
101
- * (holds); among the highest run number, RED wins on a tie (the rare same-run
102
- * re-run that published both states resolves to red, never a false green).
136
+ * This REPLACES the earlier most-recent-run-wins rule, which the 2026-08-18
137
+ * shared-runner incident disproved: run 3838's live database was force-removed
138
+ * by an overlapping run 3837, so 3838 published a failure -- and because the
139
+ * DESTROYED run carried the HIGHER run number, "most recent wins" let its red
140
+ * pin a sound sha red. Run-id order did not even match start order there (3838
141
+ * started before 3837), so "most recent run" was never reliably most-recent.
142
+ * Completed-green does not depend on run-number order at all.
143
+ *
144
+ * FAIL-CLOSED throughout: an empty set / unrecognized suffix / only-pending ->
145
+ * 'unknown'/'pending' (holds -- absent or ambiguous-without-a-completed-green
146
+ * evidence never passes). Within a SINGLE run, RED still wins a green/red tie
147
+ * (the rare same-run re-run that published both states resolves that run to red,
148
+ * never a false green); a sha with NO green run anywhere resolves to red, so a
149
+ * truly broken tree is never masked. The order/verdict comes from the workflow,
150
+ * never inferred by this reader.
103
151
  *
104
152
  * LEGACY refs (`refs/ci-status/<sha>/<state>`, no run number) carry no order; if
105
153
  * ONLY legacy refs exist we fall back to the original fail-closed precedence
106
154
  * (failure outranks success outranks pending), preserving prior behavior for
107
- * shas published before the run-ordered scheme deployed.
155
+ * shas published before the run-ordered scheme deployed. Any ordered ref takes
156
+ * precedence over legacy refs for the same sha.
108
157
  */
109
158
  export function classifyGitNativeCiStatus(refNames) {
110
- let bestOrder = -1;
111
- const statesAtBest = new Set();
159
+ // Collect the published states per run number so each run resolves to one
160
+ // verdict (red wins a same-run tie) before the cross-run comparison.
161
+ const statesByRun = new Map();
112
162
  let sawLegacyRed = false;
113
163
  let sawLegacyGreen = false;
114
164
  let sawLegacyPending = false;
@@ -127,22 +177,39 @@ export function classifyGitNativeCiStatus(refNames) {
127
177
  sawLegacyPending = true;
128
178
  continue;
129
179
  }
130
- if (order > bestOrder) {
131
- bestOrder = order;
132
- statesAtBest.clear();
133
- statesAtBest.add(state);
134
- }
135
- else if (order === bestOrder) {
136
- statesAtBest.add(state);
180
+ let set = statesByRun.get(order);
181
+ if (!set) {
182
+ set = new Set();
183
+ statesByRun.set(order, set);
137
184
  }
185
+ set.add(state);
138
186
  }
139
- // Most-recent ordered run decides (red wins a same-run tie, fail-closed).
140
- if (bestOrder >= 0) {
141
- if (statesAtBest.has('red'))
142
- return 'red';
143
- if (statesAtBest.has('green'))
144
- return 'green';
145
- if (statesAtBest.has('pending'))
187
+ if (statesByRun.size > 0) {
188
+ // Resolve each run to a single verdict (red wins a same-run tie, fail-closed),
189
+ // then apply completed-green-wins across runs.
190
+ let anyGreen = false;
191
+ let anyRed = false;
192
+ let anyPending = false;
193
+ for (const set of statesByRun.values()) {
194
+ const runVerdict = set.has('red')
195
+ ? 'red'
196
+ : set.has('green')
197
+ ? 'green'
198
+ : set.has('pending')
199
+ ? 'pending'
200
+ : 'unknown';
201
+ if (runVerdict === 'green')
202
+ anyGreen = true;
203
+ else if (runVerdict === 'red')
204
+ anyRed = true;
205
+ else if (runVerdict === 'pending')
206
+ anyPending = true;
207
+ }
208
+ if (anyGreen)
209
+ return 'green'; // a completed green run proves the tree sound
210
+ if (anyRed)
211
+ return 'red'; // no green anywhere -> a real gate red holds
212
+ if (anyPending)
146
213
  return 'pending';
147
214
  return 'unknown';
148
215
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ci-status.js","sourceRoot":"","sources":["../../src/ci/ci-status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ci-status.js","sourceRoot":"","sources":["../../src/ci/ci-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,GAErB,MAAM,kCAAkC,CAAC;AAoC1C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAmB;IACpD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,OAAO;YACV,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,6BAA6B,EAAE,CAAC;QAC1E,KAAK,SAAS;YACZ,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,4EAA4E,EAAE,CAAC;QAC1H,KAAK,KAAK;YACR,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,wFAAwF,EAAE,CAAC;QACtI,KAAK,SAAS,CAAC;QACf;YACE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,qFAAqF,EAAE,CAAC;IAChJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAmB,EAAE,GAAW;IAClE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,KAAK;YACR,OAAO,wBAAwB,CAAC,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QACpE,KAAK,SAAS,CAAC;QACf,KAAK,SAAS,CAAC;QACf,KAAK,OAAO,CAAC;QACb;YACE,OAAO,wBAAwB,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,SAAS,oBAAoB,CAAC,GAAW;IACvC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7F,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,OAAO,CAAC;IAClG,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACtG,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,WAAW,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YACxB,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAA2B;IACnE,0EAA0E;IAC1E,qEAAqE;IACrE,MAAM,WAAW,GAAG,IAAI,GAAG,EAA4B,CAAC;IACxD,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAE7B,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,KAAK,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,KAAK,KAAK,KAAK;gBAAE,YAAY,GAAG,IAAI,CAAC;iBACpC,IAAI,KAAK,KAAK,OAAO;gBAAE,cAAc,GAAG,IAAI,CAAC;;gBAC7C,gBAAgB,GAAG,IAAI,CAAC;YAC7B,SAAS;QACX,CAAC;QACD,IAAI,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,IAAI,GAAG,EAAe,CAAC;YAC7B,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC9B,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,WAAW,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACzB,+EAA+E;QAC/E,+CAA+C;QAC/C,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,IAAI,UAAU,GAAG,KAAK,CAAC;QACvB,KAAK,MAAM,GAAG,IAAI,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YACvC,MAAM,UAAU,GAAgB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;gBAC5C,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;oBAChB,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;wBAClB,CAAC,CAAC,SAAS;wBACX,CAAC,CAAC,SAAS,CAAC;YAClB,IAAI,UAAU,KAAK,OAAO;gBAAE,QAAQ,GAAG,IAAI,CAAC;iBACvC,IAAI,UAAU,KAAK,KAAK;gBAAE,MAAM,GAAG,IAAI,CAAC;iBACxC,IAAI,UAAU,KAAK,SAAS;gBAAE,UAAU,GAAG,IAAI,CAAC;QACvD,CAAC;QACD,IAAI,QAAQ;YAAE,OAAO,OAAO,CAAC,CAAC,8CAA8C;QAC5E,IAAI,MAAM;YAAE,OAAO,KAAK,CAAC,CAAC,6CAA6C;QACvE,IAAI,UAAU;YAAE,OAAO,SAAS,CAAC;QACjC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,yDAAyD;IACzD,IAAI,YAAY;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,cAAc;QAAE,OAAO,OAAO,CAAC;IACnC,IAAI,gBAAgB;QAAE,OAAO,SAAS,CAAC;IACvC,OAAO,SAAS,CAAC;AACnB,CAAC;AAyCD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAmB,EAAE,oBAA6B;IACrF,IAAI,MAAM,KAAK,KAAK,IAAI,CAAC,oBAAoB;QAAE,OAAO,MAAM,CAAC;IAC7D,IAAI,MAAM,KAAK,OAAO,IAAI,oBAAoB;QAAE,OAAO,SAAS,CAAC;IACjE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,MAAyB;IACrE,OAAO,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE;QAC/B,IAAI,IAAI,GAAa,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;YACrE,IAAI,GAAG,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,yBAAyB,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC,CAAC;AACJ,CAAC"}
@@ -14,10 +14,37 @@
14
14
  * the exact state it would approve). First drift arms a full-pass re-review (no
15
15
  * escalation); a second consecutive drift files ONE needs_operator escalation
16
16
  * (deduped in the DB by kind+focus). Cleared on a coverage-complete exit and on
17
- * the coverage-escalation re-arm edge. In-memory (a restart resets to a fresh
18
- * respawn allowance -- the safe direction; it never over-escalates).
17
+ * the coverage-escalation re-arm edge.
18
+ *
19
+ * DURABLE: the count is a spent budget toward the review_coverage_incomplete
20
+ * escalation, persisted to product_focuses.below_coverage_drift_count so the
21
+ * second drift is still reached when a daemon re-exec falls between the two --
22
+ * "a spent budget stays spent" (TEL-2). recordBelowCoverageDrift /
23
+ * resetCoverageDriftState write through; hydrateBelowCoverageDriftCount restores
24
+ * it on boot. (Mutate ONLY through those functions so the write-through holds --
25
+ * the raw Map export remains for the read/reset call sites.)
19
26
  */
20
27
  export declare const belowCoverageDriftCounts: Map<string, number>;
28
+ /**
29
+ * Durable write-through seam for the below-coverage drift count. Registered once
30
+ * at boot (setCoverageDriftPersister); called best-effort (fire-and-forget).
31
+ * Unset in tests -> pure in-memory.
32
+ */
33
+ export type CoverageDriftPersister = (focusId: string, count: number) => void;
34
+ /** Register (or clear, with null) the durable write-through persister at boot. */
35
+ export declare function setCoverageDriftPersister(fn: CoverageDriftPersister | null): void;
36
+ /**
37
+ * Increment a focus's consecutive below-coverage drift streak by one, write it
38
+ * through durably, and return the new streak. The single mutation path for the
39
+ * increment so the durable column cannot drift from the in-memory Map.
40
+ */
41
+ export declare function recordBelowCoverageDrift(focusId: string): number;
42
+ /**
43
+ * Restore a focus's below-coverage drift count from its persisted column, the
44
+ * first time the daemon sees the focus after a (re-)start. Non-clobbering +
45
+ * first-sight-only; skips a focus with no persisted streak.
46
+ */
47
+ export declare function hydrateBelowCoverageDriftCount(focusId: string, count: number): void;
21
48
  /**
22
49
  * Focuses whose NEXT review must be a full pass (drift was detected, so the
23
50
  * prior coverage cannot be trusted incrementally). Consumed + cleared by the
@@ -1 +1 @@
1
- {"version":3,"file":"coverage-drift-state.d.ts","sourceRoot":"","sources":["../../src/completion/coverage-drift-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,qBAA4B,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,aAAoB,CAAC;AAE5D;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAG7D;AAED,wDAAwD;AACxD,wBAAgB,iCAAiC,IAAI,IAAI,CAGxD"}
1
+ {"version":3,"file":"coverage-drift-state.d.ts","sourceRoot":"","sources":["../../src/completion/coverage-drift-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,wBAAwB,qBAA4B,CAAC;AAElE;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AAI9E,kFAAkF;AAClF,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,sBAAsB,GAAG,IAAI,GAAG,IAAI,CAEjF;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAInF;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,aAAoB,CAAC;AAE5D;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAO7D;AAED,wDAAwD;AACxD,wBAAgB,iCAAiC,IAAI,IAAI,CAGxD"}
@@ -14,10 +14,45 @@
14
14
  * the exact state it would approve). First drift arms a full-pass re-review (no
15
15
  * escalation); a second consecutive drift files ONE needs_operator escalation
16
16
  * (deduped in the DB by kind+focus). Cleared on a coverage-complete exit and on
17
- * the coverage-escalation re-arm edge. In-memory (a restart resets to a fresh
18
- * respawn allowance -- the safe direction; it never over-escalates).
17
+ * the coverage-escalation re-arm edge.
18
+ *
19
+ * DURABLE: the count is a spent budget toward the review_coverage_incomplete
20
+ * escalation, persisted to product_focuses.below_coverage_drift_count so the
21
+ * second drift is still reached when a daemon re-exec falls between the two --
22
+ * "a spent budget stays spent" (TEL-2). recordBelowCoverageDrift /
23
+ * resetCoverageDriftState write through; hydrateBelowCoverageDriftCount restores
24
+ * it on boot. (Mutate ONLY through those functions so the write-through holds --
25
+ * the raw Map export remains for the read/reset call sites.)
19
26
  */
20
27
  export const belowCoverageDriftCounts = new Map();
28
+ let coveragePersister = null;
29
+ /** Register (or clear, with null) the durable write-through persister at boot. */
30
+ export function setCoverageDriftPersister(fn) {
31
+ coveragePersister = fn;
32
+ }
33
+ /**
34
+ * Increment a focus's consecutive below-coverage drift streak by one, write it
35
+ * through durably, and return the new streak. The single mutation path for the
36
+ * increment so the durable column cannot drift from the in-memory Map.
37
+ */
38
+ export function recordBelowCoverageDrift(focusId) {
39
+ const streak = (belowCoverageDriftCounts.get(focusId) ?? 0) + 1;
40
+ belowCoverageDriftCounts.set(focusId, streak);
41
+ coveragePersister?.(focusId, streak);
42
+ return streak;
43
+ }
44
+ /**
45
+ * Restore a focus's below-coverage drift count from its persisted column, the
46
+ * first time the daemon sees the focus after a (re-)start. Non-clobbering +
47
+ * first-sight-only; skips a focus with no persisted streak.
48
+ */
49
+ export function hydrateBelowCoverageDriftCount(focusId, count) {
50
+ if (belowCoverageDriftCounts.has(focusId))
51
+ return; // in-memory wins during a run
52
+ if (count <= 0)
53
+ return; // no streak to restore
54
+ belowCoverageDriftCounts.set(focusId, count);
55
+ }
21
56
  /**
22
57
  * Focuses whose NEXT review must be a full pass (drift was detected, so the
23
58
  * prior coverage cannot be trusted incrementally). Consumed + cleared by the
@@ -35,8 +70,13 @@ export const forceFullPassReviewFocuses = new Set();
35
70
  * mirrors resetDeliveryAdvancementGuard's role for the team_failure re-arm.
36
71
  */
37
72
  export function resetCoverageDriftState(focusId) {
73
+ const hadStreak = (belowCoverageDriftCounts.get(focusId) ?? 0) > 0;
38
74
  belowCoverageDriftCounts.delete(focusId);
39
75
  forceFullPassReviewFocuses.delete(focusId);
76
+ // Write the cleared budget through -- but only when it stood above 0, to avoid
77
+ // a needless DB write on every routed exit / re-arm (the streak is usually 0).
78
+ if (hadStreak)
79
+ coveragePersister?.(focusId, 0);
40
80
  }
41
81
  /** Test reset hook for the in-memory drift trackers. */
42
82
  export function __resetCoverageDriftStateForTests() {
@@ -1 +1 @@
1
- {"version":3,"file":"coverage-drift-state.js","sourceRoot":"","sources":["../../src/completion/coverage-drift-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,GAAG,EAAkB,CAAC;AAElE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,GAAG,EAAU,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,wBAAwB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,0BAA0B,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,iCAAiC;IAC/C,wBAAwB,CAAC,KAAK,EAAE,CAAC;IACjC,0BAA0B,CAAC,KAAK,EAAE,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"coverage-drift-state.js","sourceRoot":"","sources":["../../src/completion/coverage-drift-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,GAAG,EAAkB,CAAC;AASlE,IAAI,iBAAiB,GAAkC,IAAI,CAAC;AAE5D,kFAAkF;AAClF,MAAM,UAAU,yBAAyB,CAAC,EAAiC;IACzE,iBAAiB,GAAG,EAAE,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,MAAM,MAAM,GAAG,CAAC,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAChE,wBAAwB,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9C,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACrC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAAC,OAAe,EAAE,KAAa;IAC3E,IAAI,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,8BAA8B;IACjF,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,CAAC,uBAAuB;IAC/C,wBAAwB,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,GAAG,EAAU,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,MAAM,SAAS,GAAG,CAAC,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACnE,wBAAwB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,0BAA0B,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC3C,+EAA+E;IAC/E,+EAA+E;IAC/E,IAAI,SAAS;QAAE,iBAAiB,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,iCAAiC;IAC/C,wBAAwB,CAAC,KAAK,EAAE,CAAC;IACjC,0BAA0B,CAAC,KAAK,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Close half for the review_coverage_incomplete spawn block (delivery 7, Task 2).
3
+ *
4
+ * applyRespawnGuard parks EVERY spawn (coding AND review) while a
5
+ * review_coverage_incomplete escalation is open. The drift-streak breaker files
6
+ * it precisely because the focus branch kept advancing DURING each review pass,
7
+ * so a clean review never covered the exact state it would approve. That creates
8
+ * a deadlock: with review spawns blocked, no review can run to complete coverage,
9
+ * so the escalation can never clear at the review-exit site.
10
+ *
11
+ * The clearing condition is SPAWN-INDEPENDENT and distinct from delivery
12
+ * advancement (Task 1's signal): the branch has STABILIZED. The escalation
13
+ * records the branch tip at filing (metadata.current_tip). When the focus branch
14
+ * tip has not moved since -- no new commits landed -- a re-review can now cover
15
+ * the exact state, so the daemon resolves the escalation itself. applyRespawnGuard
16
+ * then permits a review spawn; if the branch has in fact re-drifted, the next
17
+ * review re-files the escalation, and the whole cycle is bounded by the existing
18
+ * review_cycle_cap. No human act.
19
+ *
20
+ * Pure decision (isCoverageBranchStable) + a thin reconcile whose git read,
21
+ * escalation list, and resolve are injected so it is unit-testable with no real
22
+ * git or DB.
23
+ */
24
+ import { listOpenFocusEscalationsOfKind, markEscalationResolved as defaultMarkEscalationResolved } from '../queries/issues.js';
25
+ import { resolveRef as defaultResolveRef } from '../git/index.js';
26
+ /**
27
+ * Pure decision: has the focus branch STABILIZED at the tip recorded when the
28
+ * coverage-incomplete escalation was filed? True only when BOTH tips are known
29
+ * and equal (no commits landed since filing). A missing recorded tip (a legacy
30
+ * filing, or a null live read) is NOT stable -- the hold is kept, so a re-review
31
+ * is never armed over an unknown/moving branch. Exported for direct unit testing.
32
+ */
33
+ export declare function isCoverageBranchStable(recordedTip: string | null | undefined, liveTip: string | null): boolean;
34
+ export interface CoverageStabilizedDeps {
35
+ /** List the focus's open review_coverage_incomplete rows (id + metadata). */
36
+ listOpen: typeof listOpenFocusEscalationsOfKind;
37
+ /** Resolve the focus branch ref to its live tip SHA (null when unreadable). */
38
+ resolveRef: typeof defaultResolveRef;
39
+ /** Resolve a single escalation row (the close-half write). */
40
+ markEscalationResolved: typeof defaultMarkEscalationResolved;
41
+ }
42
+ export interface CoverageStabilizedContext {
43
+ focusId: string;
44
+ focusName: string;
45
+ /** Pinned focus branch identity (product_focuses.branch_name), or null. */
46
+ branchName: string | null;
47
+ /** Product repo path -- the branch ref lives in its git dir (worktrees share it). */
48
+ repoPath: string;
49
+ }
50
+ /**
51
+ * Reconcile ONE focus's open review_coverage_incomplete hold: resolve it iff the
52
+ * focus branch has stabilized at the tip recorded at filing. Best-effort: any
53
+ * failure is logged and swallowed so the caller's cascade pass is never blocked.
54
+ * Returns the number of holds resolved (0 or 1 in practice -- the kind is
55
+ * cause-key deduped to one open row per focus).
56
+ */
57
+ export declare function reconcileCoverageIncompleteHold(ctx: CoverageStabilizedContext, deps?: CoverageStabilizedDeps): Promise<number>;
58
+ //# sourceMappingURL=coverage-stabilized.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-stabilized.d.ts","sourceRoot":"","sources":["../../src/completion/coverage-stabilized.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,8BAA8B,EAAE,sBAAsB,IAAI,6BAA6B,EAA8C,MAAM,sBAAsB,CAAC;AAC3K,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAKlE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,OAAO,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAET;AAED,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,8BAA8B,CAAC;IAChD,+EAA+E;IAC/E,UAAU,EAAE,OAAO,iBAAiB,CAAC;IACrC,8DAA8D;IAC9D,sBAAsB,EAAE,OAAO,6BAA6B,CAAC;CAC9D;AAQD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,+BAA+B,CACnD,GAAG,EAAE,yBAAyB,EAC9B,IAAI,GAAE,sBAAoC,GACzC,OAAO,CAAC,MAAM,CAAC,CAkCjB"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Close half for the review_coverage_incomplete spawn block (delivery 7, Task 2).
3
+ *
4
+ * applyRespawnGuard parks EVERY spawn (coding AND review) while a
5
+ * review_coverage_incomplete escalation is open. The drift-streak breaker files
6
+ * it precisely because the focus branch kept advancing DURING each review pass,
7
+ * so a clean review never covered the exact state it would approve. That creates
8
+ * a deadlock: with review spawns blocked, no review can run to complete coverage,
9
+ * so the escalation can never clear at the review-exit site.
10
+ *
11
+ * The clearing condition is SPAWN-INDEPENDENT and distinct from delivery
12
+ * advancement (Task 1's signal): the branch has STABILIZED. The escalation
13
+ * records the branch tip at filing (metadata.current_tip). When the focus branch
14
+ * tip has not moved since -- no new commits landed -- a re-review can now cover
15
+ * the exact state, so the daemon resolves the escalation itself. applyRespawnGuard
16
+ * then permits a review spawn; if the branch has in fact re-drifted, the next
17
+ * review re-files the escalation, and the whole cycle is bounded by the existing
18
+ * review_cycle_cap. No human act.
19
+ *
20
+ * Pure decision (isCoverageBranchStable) + a thin reconcile whose git read,
21
+ * escalation list, and resolve are injected so it is unit-testable with no real
22
+ * git or DB.
23
+ */
24
+ import { listOpenFocusEscalationsOfKind, markEscalationResolved as defaultMarkEscalationResolved, REVIEW_COVERAGE_INCOMPLETE_ESCALATION_KIND } from '../queries/issues.js';
25
+ import { resolveRef as defaultResolveRef } from '../git/index.js';
26
+ import { createLogger } from '../shell/log.js';
27
+ const log = createLogger({ module: 'focus-executor' });
28
+ /**
29
+ * Pure decision: has the focus branch STABILIZED at the tip recorded when the
30
+ * coverage-incomplete escalation was filed? True only when BOTH tips are known
31
+ * and equal (no commits landed since filing). A missing recorded tip (a legacy
32
+ * filing, or a null live read) is NOT stable -- the hold is kept, so a re-review
33
+ * is never armed over an unknown/moving branch. Exported for direct unit testing.
34
+ */
35
+ export function isCoverageBranchStable(recordedTip, liveTip) {
36
+ return !!recordedTip && !!liveTip && recordedTip === liveTip;
37
+ }
38
+ const defaultDeps = {
39
+ listOpen: listOpenFocusEscalationsOfKind,
40
+ resolveRef: defaultResolveRef,
41
+ markEscalationResolved: defaultMarkEscalationResolved,
42
+ };
43
+ /**
44
+ * Reconcile ONE focus's open review_coverage_incomplete hold: resolve it iff the
45
+ * focus branch has stabilized at the tip recorded at filing. Best-effort: any
46
+ * failure is logged and swallowed so the caller's cascade pass is never blocked.
47
+ * Returns the number of holds resolved (0 or 1 in practice -- the kind is
48
+ * cause-key deduped to one open row per focus).
49
+ */
50
+ export async function reconcileCoverageIncompleteHold(ctx, deps = defaultDeps) {
51
+ if (!ctx.branchName)
52
+ return 0; // no pinned branch identity -> nothing to compare
53
+ let open;
54
+ try {
55
+ open = await deps.listOpen(ctx.focusId, REVIEW_COVERAGE_INCOMPLETE_ESCALATION_KIND);
56
+ }
57
+ catch (err) {
58
+ log.warn(`[coverage-stabilized] list failed for "${ctx.focusName}": ${err.message}`);
59
+ return 0;
60
+ }
61
+ if (open.length === 0)
62
+ return 0;
63
+ const liveTip = deps.resolveRef(ctx.repoPath, ctx.branchName);
64
+ let resolved = 0;
65
+ for (const row of open) {
66
+ const recordedTip = row.metadata?.current_tip ?? null;
67
+ if (!isCoverageBranchStable(recordedTip, liveTip))
68
+ continue;
69
+ try {
70
+ await deps.markEscalationResolved(row.id, 'Auto-resolved by the daemon: the focus branch stabilized at the tip recorded when coverage-incomplete ' +
71
+ 'was filed (no commits since), so a re-review can now complete coverage -- the spawn block cleared.');
72
+ resolved++;
73
+ }
74
+ catch (err) {
75
+ log.warn(`[coverage-stabilized] resolve failed for escalation ${row.id} on "${ctx.focusName}": ${err.message}`);
76
+ }
77
+ }
78
+ if (resolved > 0) {
79
+ log.info(`[coverage-stabilized] Resolved ${resolved} review_coverage_incomplete hold(s) for "${ctx.focusName}" ` +
80
+ `-- branch stabilized at ${liveTip?.slice(0, 12)}; review spawns unblocked with no human act.`);
81
+ }
82
+ return resolved;
83
+ }
84
+ //# sourceMappingURL=coverage-stabilized.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-stabilized.js","sourceRoot":"","sources":["../../src/completion/coverage-stabilized.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,8BAA8B,EAAE,sBAAsB,IAAI,6BAA6B,EAAE,0CAA0C,EAAE,MAAM,sBAAsB,CAAC;AAC3K,OAAO,EAAE,UAAU,IAAI,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAEvD;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,WAAsC,EACtC,OAAsB;IAEtB,OAAO,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,OAAO,IAAI,WAAW,KAAK,OAAO,CAAC;AAC/D,CAAC;AAWD,MAAM,WAAW,GAA2B;IAC1C,QAAQ,EAAE,8BAA8B;IACxC,UAAU,EAAE,iBAAiB;IAC7B,sBAAsB,EAAE,6BAA6B;CACtD,CAAC;AAWF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,GAA8B,EAC9B,OAA+B,WAAW;IAE1C,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,CAAC,CAAC,CAAC,kDAAkD;IACjF,IAAI,IAAqE,CAAC;IAC1E,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,0CAA0C,CAAC,CAAC;IACtF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,IAAI,CAAC,0CAA0C,GAAG,CAAC,SAAS,MAAO,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAChG,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEhC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAC9D,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,WAAW,GAAI,GAAG,CAAC,QAAQ,EAAE,WAAyC,IAAI,IAAI,CAAC;QACrF,IAAI,CAAC,sBAAsB,CAAC,WAAW,EAAE,OAAO,CAAC;YAAE,SAAS;QAC5D,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,sBAAsB,CAC/B,GAAG,CAAC,EAAE,EACN,wGAAwG;gBACtG,oGAAoG,CACvG,CAAC;YACF,QAAQ,EAAE,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,IAAI,CAAC,uDAAuD,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,SAAS,MAAO,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7H,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACjB,GAAG,CAAC,IAAI,CACN,kCAAkC,QAAQ,4CAA4C,GAAG,CAAC,SAAS,IAAI;YACvG,2BAA2B,OAAO,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,8CAA8C,CAC/F,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -70,6 +70,27 @@ export interface DeliveryAdvancementDecision {
70
70
  noAdvancementCycles: number;
71
71
  thresholdReached: boolean;
72
72
  }
73
+ /**
74
+ * Durable write-through seam for the cross-respawn no-progress state. Registered
75
+ * once at daemon boot (setDeliveryAdvancementPersister); called best-effort
76
+ * (fire-and-forget) whenever the state changes, and with null when the guard is
77
+ * reset for a focus. Unset in tests -> the guard stays pure in-memory. This is
78
+ * what makes a spent no-advancement budget survive the auto-updater's re-exec
79
+ * (module-scope state alone reset noAdvancementCycles to 0 every restart, handing
80
+ * a focus about to trip its team_failure halt a fresh budget).
81
+ */
82
+ export type DeliveryAdvancementPersister = (focusId: string, state: DeliveryAdvancementState | null) => void;
83
+ /** Register (or clear, with null) the durable write-through persister at boot. */
84
+ export declare function setDeliveryAdvancementPersister(fn: DeliveryAdvancementPersister | null): void;
85
+ /**
86
+ * Restore a focus's no-progress watchdog state from its persisted column, the
87
+ * FIRST time the daemon sees the focus after a (re-)start. Non-clobbering: a
88
+ * state already tracked in memory this run wins (hydrate never overwrites a live
89
+ * cycle). Restoring the SNAPSHOT alongside the counter is essential -- a bare
90
+ * counter would rebase to 0 on the first post-restart cycle for lack of a prior
91
+ * to compare against, silently forgiving the streak.
92
+ */
93
+ export declare function hydrateDeliveryAdvancementState(focusId: string, state: DeliveryAdvancementState | null): void;
73
94
  export declare function takeDeliveryAdvancementSnapshot(deliveries: Pick<FocusDeliveryInfo, 'id' | 'executionStatus'>[], issues?: Pick<FocusIssueInfo, 'id' | 'status'>[], focusHeadSha?: string | null): DeliveryAdvancementSnapshot;
74
95
  export declare function deliveryAdvancementSnapshotsEqual(a: DeliveryAdvancementSnapshot, b: DeliveryAdvancementSnapshot): boolean;
75
96
  export declare function recordDeliveryAdvancementCycle(focusId: string, deliveries: Pick<FocusDeliveryInfo, 'id' | 'executionStatus'>[], issues: Pick<FocusIssueInfo, 'id' | 'status'>[], focusHeadSha: string | null, threshold: number,
@@ -1 +1 @@
1
- {"version":3,"file":"delivery-advancement-guard.d.ts","sourceRoot":"","sources":["../../src/completion/delivery-advancement-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE3E,MAAM,WAAW,2BAA2B;IAC1C;iFAC6E;IAC7E,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;mEAC+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB;+DAC2D;IAC3D,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,2BAA2B,CAAC;IACtC,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAiBD,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,iBAAiB,CAAC,EAAE,EAC/D,MAAM,GAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,QAAQ,CAAC,EAAO,EACpD,YAAY,GAAE,MAAM,GAAG,IAAW,GACjC,2BAA2B,CAU7B;AAED,wBAAgB,iCAAiC,CAC/C,CAAC,EAAE,2BAA2B,EAC9B,CAAC,EAAE,2BAA2B,GAC7B,OAAO,CAaT;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,iBAAiB,CAAC,EAAE,EAC/D,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,QAAQ,CAAC,EAAE,EAC/C,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM;AACjB,+EAA+E;AAC/E,KAAK,GAAE,MAAmB;AAC1B;;;;;GAKG;AACH,OAAO,SAAI,GACV,2BAA2B,CAqC7B;AAED,wBAAgB,6BAA6B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAMpE"}
1
+ {"version":3,"file":"delivery-advancement-guard.d.ts","sourceRoot":"","sources":["../../src/completion/delivery-advancement-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE3E,MAAM,WAAW,2BAA2B;IAC1C;iFAC6E;IAC7E,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;mEAC+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB;+DAC2D;IAC3D,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,2BAA2B,CAAC;IACtC,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAID;;;;;;;;GAQG;AACH,MAAM,MAAM,4BAA4B,GAAG,CACzC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,wBAAwB,GAAG,IAAI,KACnC,IAAI,CAAC;AAIV,kFAAkF;AAClF,wBAAgB,+BAA+B,CAAC,EAAE,EAAE,4BAA4B,GAAG,IAAI,GAAG,IAAI,CAE7F;AAQD;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,IAAI,CAIN;AAeD,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,iBAAiB,CAAC,EAAE,EAC/D,MAAM,GAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,QAAQ,CAAC,EAAO,EACpD,YAAY,GAAE,MAAM,GAAG,IAAW,GACjC,2BAA2B,CAU7B;AAED,wBAAgB,iCAAiC,CAC/C,CAAC,EAAE,2BAA2B,EAC9B,CAAC,EAAE,2BAA2B,GAC7B,OAAO,CAaT;AAED,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,iBAAiB,CAAC,EAAE,EAC/D,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,QAAQ,CAAC,EAAE,EAC/C,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM;AACjB,+EAA+E;AAC/E,KAAK,GAAE,MAAmB;AAC1B;;;;;GAKG;AACH,OAAO,SAAI,GACV,2BAA2B,CAqC7B;AAED,wBAAgB,6BAA6B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CASpE"}