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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
//! Read-time detection of a *suspected false-failed* run — a `failed` run whose
//! worker content is git-verified in source, but which no `run merge`
//! transaction recorded (issue `raw-git-selfmerge-false-failed`, epic
//! `lifecycle-architecture-review`).
//!
//! ## The thin-model tradeoff this surfaces
//!
//! Under the thin supervisor, `run merge` is the ONLY success truth (invariant 6
//! / design §2.1b): it records a `merge.started` transaction, stamps a typed
//! [`ReportOrigin::RunMerge`](octl_core::ReportOrigin) terminal report, and is the
//! only path the recovery machinery can complete. An agent that instead
//! **hand-merges its branch into source with raw git** (`git checkout main &&
//! git merge wt/...`) and then dies leaves NO merge transaction and NO typed
//! merge origin. The crash backstop (design §2.1a) then confirms the worker gone,
//! finds no merge, and synthesizes a `failed` report — even though the worker's
//! content is already integrated into source.
//!
//! This is **not data loss**: the teardown gate (invariant 5) preserves the
//! branch AND worktree on every non-explicit-merge terminal, so the work is
//! exactly where a human left it. It is an **observability tradeoff** — a run
//! reads `failed` while its content is, in fact, in source.
//!
//! ## What this module does — and deliberately does NOT do
//!
//! It is a pure, read-time, **non-mutating** hint (like its siblings
//! [`crate::run::attention`] / [`crate::run::stalled`]) computed by `run show`.
//! It **never terminalizes the run to `done`** and never re-classifies the
//! outcome. Auto-flipping a `failed` run to `done` off a branch-content
//! heuristic is exactly the inference the thin model deletes (the removed
//! git-reconcile-implies-done probe, invariant 7) — resurrecting it would let a
//! forged/coincidental branch state fake a success. So this surfaces a
//! **suspicion + remediation**, not a verdict: the human runs
//! [`run salvage`](crate::run::salvage) — which drives the skipped merge through
//! the real `run merge` machinery (recording the transaction, the typed origin,
//! the CAS-guarded fast-forward), idempotently against the already-integrated
//! content — to record the merge and terminalize the run to `done` honestly.
//!
//! ## The exact firing signature
//!
//! [`is_false_failed_suspected`] fires only when ALL of:
//!
//! - the run's status is `Failed` — a settled negative terminal. A live/pending
//! run is `attention`/`stall` territory, and a `done`/`cancelled` run has no
//! false-failed to suspect.
//! - `landed` is `true` **and** its method is
//! [`LandedMethod::GitVerified`]
//! — git's *live, authoritative* view says every branch commit is integrated
//! into the current source tip (patch-id equivalence, rebase-robust). A
//! `report-marker` landing is deliberately EXCLUDED: that marker only exists on
//! a confirmed `run merge`, which would have rolled the run to `done`, not
//! `failed` — so seeing it on a `failed` run would mean a corrupt projection,
//! not a raw-git self-merge. Requiring `git-verified` means the suspicion rests
//! on git ground truth, never on a report field.
//! - the terminal report is NOT a confirmed `run merge` (no typed `RunMerge`
//! origin / legacy `via: "explicit-merge"`). A recorded merge is the honest
//! `done` path; its absence beside git-verified-landed content is the whole
//! tell.
//! - a **fork-point (`base_sha`) is recorded** for the node. This is the stricter
//! burden of proof this consumer demands over the bare `landed` signal (llm
//! review consensus, gemini + deepseek): without a `base_sha`,
//! [`crate::run::landed`]'s ancestry safety net cannot tell a genuine landing
//! from a **never-advanced branch that merged nothing** — a branch trivially an
//! ancestor of source with zero commits reads `landed: true, git-verified` under
//! the missing-base fallback (`branch_advanced_past_base` returns `true` when
//! `base` is `None`). Firing on that would tell a user to `run salvage` a branch
//! with no work. Every normal spinoff records `base_sha` at `node.created`, so
//! requiring it suppresses only the ambiguous legacy/corrupt no-fork-point case
//! — a deliberately conservative false-negative (never a false positive).
//!
//! Every branch of that AND is load-bearing: drop the git-verified requirement
//! and a stale marker could fire it; drop the not-a-merge requirement and an
//! honest `done` run (mis-statused) could; drop the `Failed` gate and a
//! still-running raw-git-merger (not yet dead) would false-flag before the human
//! could even act; drop the `base_sha` requirement and a never-advanced branch
//! that merged nothing would falsely read as landed.
//!
//! ## Accepted blind spots (false negatives, never false positives)
//!
//! The signal rests on git's *history* view of the recorded worker branch, so it
//! deliberately does NOT fire — but also never mis-fires — in these shapes:
//!
//! - **Single-worker only.** `run show` gates this on `node_count == 1` (mirroring
//! [`crate::run::attention`]): the reporting node is `n-0001`, so for a
//! multi-node fan-out a *child* worker that raw-selfmerged then died is NOT
//! surfaced (its `n-0001` may be a driver). Per-node false-failed is the
//! delegated `per-node-run` follow-up.
//! - **Squash merges.** A `git merge --squash` collapses the branch's commits into
//! one new patch-id, so `git cherry` sees `+` and ancestry is false — `landed`
//! reads false and the hint stays silent. The content IS in source but git
//! cannot prove the *branch's* commits landed.
//! - **Post-merge extra commits.** If the worker raw-merged then committed MORE on
//! the branch before dying, `git cherry` sees a `+` for the extra commit and
//! `landed` reads false — the run's *earlier* work is in source but the branch
//! is no longer fully integrated, so the hint stays silent.
//!
//! In every case a false negative is strictly safer than falsely telling a user
//! their work is already merged.
use ReportOrigin;
use Value;
use crateLandedMethod;
/// Does this run look like a *false-failed* raw-git self-merge — `failed`, yet
/// git confirms the branch content is in source and no `run merge` recorded it?
///
/// Pure over its inputs (all already computed by `run show`): the manifest
/// status, the git-verified [`crate::run::landed`] signal, and the reporting
/// node's terminal report. Touches no event/reducer/schema/lock path and never
/// mutates — see the module docs for why this must stay a hint, never a verdict.
///
/// `status_is_failed` is passed as a bool rather than a `Status` so the caller
/// keeps the single source of truth for "is this the failed terminal" (and so
/// this stays trivially testable without constructing a `Status`).
/// `base_sha_present` is `node.base_sha.is_some()` — the recorded fork point; see
/// the module docs for why it is a required burden of proof here (it closes the
/// never-advanced-branch false positive the bare `landed` signal admits when the
/// fork point is unknown).
/// The stable machine `reason` string on [`FalseFailedView`]. A JSON consumer can
/// branch on this without parsing [`FalseFailedView::resume_hint`].
pub const FALSE_FAILED_REASON: &str =
"branch content is git-verified in source but no `run merge` recorded it (raw-git self-merge?)";
/// Resume context `run show` surfaces for a suspected false-failed run so a human
/// can settle it honestly without re-deriving the git state by hand.
///
/// Built only when [`is_false_failed_suspected`] holds; a `None` on the payload
/// means the run is not a suspected false-failed.
/// The human/JSON remediation hint for a suspected false-failed run: the honest
/// way to settle it is [`run salvage`](crate::run::salvage), which drives the
/// skipped merge through the real `run merge` machinery — idempotent against the
/// already-integrated content — so the run terminalizes to `done` with a recorded
/// transaction and typed origin, instead of sitting `failed` while its work is in
/// source. `run cancel` is the alternative when the human accepts the `failed`
/// (the branch stays preserved either way).
///
/// The run id is single-quoted via [`shell_single_quote`] so the emitted command
/// stays a safe copy-paste even if it carries a shell metacharacter (ids are
/// tool-generated, but the hint is meant to be pasted, so it must not break or
/// inject). Phrased identically to [`crate::run::attention::resume_hint`]'s
/// `run salvage` pointer so the two read the same.
/// Wrap `s` in single quotes for safe shell copy-paste, escaping any embedded
/// single quote with the standard `'\''` idiom (close-quote, escaped quote,
/// reopen-quote). Mirrors [`crate::run::attention`]'s helper — a tool-generated
/// run id won't normally need it, but the hint is user-facing copy-paste, so it
/// must never break on a stray metacharacter.