1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//! Env-parsing helpers for the pipeline's tunable timeouts, plus the gate
//! escalation threshold constant.
//!
//! Given its own module rather than folded back into the thin `main.rs`
//! crate root (CONTEXT.md left this to discretion): each parser's tests
//! (`parse_gate_timeout_env_override`, `parse_checkout_lock_timeout_defaults_and_parses`)
//! were carried here verbatim per the plan's test-attribution rule. Keeping
//! this cluster in its own file keeps configuration parsing and its tests out
//! of the thin crate root; `main.rs` retains only the test for its own
//! `project_root` helper.
/// A pending gate becomes visually urgent after thirty minutes without an
/// answer. The banner remains visible before and after this threshold.
pub const GATE_ESCALATION_THRESHOLD_SECS: u64 = 30 * 60;
/// Parse `DEVFLOW_GATE_TIMEOUT_SECS`'s raw value, falling back to 3 days on
/// an absent or unparsable value. Pure (no env access) so it's unit-testable
/// without mutating process-global env.
/// How long a background gate poll waits for a human response, configurable
/// via `DEVFLOW_GATE_TIMEOUT_SECS` (defaults to 3 days).
///
/// This bounds how long a monitor stays alive HOLDING THE PHASE LOCK while
/// parked at a gate — it does not bound how long the operator has to answer.
/// The gate request and the phase state are files; nothing expires them, and
/// timing out here is a clean resumable stop (`run_gate_with_timeout` emits
/// `gate_timeout` and returns `Err` — no abort, no cleanup, no state clear),
/// so an answer given a week later is still an answer. What the operator
/// loses past the timeout is only the automatic pickup: with no monitor left
/// polling, the written response sits unconsumed until a `devflow resume`
/// drives it.
///
/// Three days rather than the original seven: it still covers a gate that
/// fires on a Friday evening and is answered on Monday morning (~60h), which
/// is the longest absence this project treats as routine, while capping the
/// "immortal parked monitor" case that wedged phase 35.1 for nine hours in
/// 2026-08. Raise it via the env var for a genuinely unattended long run.
pub
/// Parse `DEVFLOW_FOREGROUND_GATE_TIMEOUT_SECS`, falling back to 60s. Pure
/// (no env access) so it's unit-testable without mutating process-global env.
/// How long the FOREGROUND `devflow ship --phase` manual override (WR-02,
/// phase 20 review) waits for a re-opened Ship gate to be answered before
/// failing fast, configurable via `DEVFLOW_FOREGROUND_GATE_TIMEOUT_SECS`
/// (defaults to 60s).
///
/// Every other caller of `finish_workflow`/`run_gate` runs inside a detached
/// monitor process, so [`gate_timeout_secs`]'s multi-day production default
/// is invisible to an operator's terminal. `ship_override` calls
/// `finish_workflow` directly from the foreground CLI — if a terminal-hook
/// failure reopens the Ship gate, waiting out the multi-day default would
/// block the operator's shell for however long the gate takes to resolve.
/// This bound only caps how long the FOREGROUND wait can run before erroring
/// out with an actionable message; it does not weaken the fail-closed
/// terminal-Ship invariant — an unanswered gate still fails the operation
/// entirely, exactly as [`gate_timeout_secs`]'s timeout does today, just
/// after seconds instead of days.
pub
/// Parse `DEVFLOW_CHECKOUT_LOCK_TIMEOUT_SECS`, falling back to 120s. Pure
/// (no env access) so it's unit-testable without mutating process-global env.
/// How long a caller waits out a sibling phase's short critical section on
/// the project-wide checkout lock before giving up, configurable via
/// `DEVFLOW_CHECKOUT_LOCK_TIMEOUT_SECS` (defaults to 120s) — generous
/// relative to the seconds the lock is held for, tiny relative to a gate
/// wait.
pub
/// Parse the raw value of the sweep's max-unattended-age override. Pure (no
/// env access) so it's unit-testable without mutating process-global env.
/// Both an unparsable value and an explicit zero fall back to the three-day
/// default — the fail-safe direction matters: a threshold of zero would
/// make an invoked sweep reap every open gate on the machine on its next
/// run, so a typo or an empty override must never resolve to "reap
/// everything."
/// How long an open gate may sit unattended before an invoked `devflow gate
/// sweep` is willing to call it abandoned. This value gates nothing on its
/// own — the sweep it feeds is on-demand only (23-RESEARCH.md Open Question
/// 3); nothing schedules it.
///
/// Held EQUAL to [`gate_timeout_secs`], where it was previously six hours
/// against that function's seven days. The old split treated "how long to
/// wait for a human" and "how long before a sweep gives up on one" as
/// independent questions. They are not, because the sweep does not merely
/// report: `Gates::reap` writes an `abort:` response, and any monitor still
/// polling that gate consumes it on its next read and tears the phase down
/// via `abort()` — which clears state and deregisters, unlike the clean
/// resumable stop a poll timeout produces. A threshold SHORTER than the poll
/// timeout therefore means an operator who runs a sweep to tidy up abandoned
/// work destroys the run they were deliberately holding open over a weekend,
/// machine-wide across every registered root. Equality is the smallest value
/// that cannot do that: past the poll timeout, nothing is waiting any more.
///
/// Known edge at equality: a gate reaches this age at roughly the moment its
/// own poll gives up, so a sweep invoked inside that window can still catch a
/// live poller mid-backoff (`poll_response` caps its sleep at 60s) and turn
/// what would have been a timeout into an abort. Set this above
/// `DEVFLOW_GATE_TIMEOUT_SECS` if that distinction matters for a given run.
pub