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
//! Shared primitives every task script generator needs.
//!
//! Split out of the former single-file `task.rs` (Refs #390): the nested-shell
//! work pushed it past the 500-line health limit. Nothing here changed in the
//! move — the generated bytes are identical.
use crateslugify_identifier;
use crateResource;
/// GH-254's verdict, emitted on STDERR from inside the generated script when
/// the command exited 0 and the re-asserted `completion_check` still said no.
///
/// A `const` because `core::executor::failure_text` classifies on it (Refs
/// #390). "The command failed" and "the command SUCCEEDED and its own check
/// still fails" are different diagnoses with different next actions, and forjar
/// printed both as `exit code 1:` — #390's reporter spent six llama.cpp builds
/// debugging a compiler error that had never happened. Producer and classifier
/// share this string so they cannot drift apart, and the text is byte-identical
/// to what shipped, so every existing `not-converged` substring assertion keeps
/// holding.
///
/// It contains no single quote, which is what keeps the `echo '...'` below safe
/// and what any future edit must preserve.
pub const NOT_CONVERGED_MARKER: &str =
"task=not-converged: command exited 0 but completion_check still fails";
/// Slugified service id used in `/tmp/forjar-svc-<rid>.{pid,log}` paths.
///
/// FJ-154: the resource name flows into shared filesystem paths; slugify it to
/// `[A-Za-z0-9._-]` so it can never split a redirect target or inject shell.
pub
/// Extract absolute binary path from a command string.
///
/// Handles: `nohup /path/bin ...`, `sudo /path/bin`, `/path/bin --args`,
/// `LD_LIBRARY_PATH=/foo nohup /path/bin ...`.
pub
/// A heredoc delimiter that provably does not appear in `body`.
///
/// Refs #390-E. The generator used a fixed `FORJAR_TIMEOUT`, so a command
/// containing that word on a line of its own closed the heredoc EARLY and the
/// remaining lines ran in the OUTER shell. Reproduced against 1.24.0: the script
/// printed `FORJAR_TIMEOUT: command not found` and then executed the lines that
/// were supposed to be inside. That is the C8 delimiter-collision class
/// `shell_escape.rs` documents as fixed for `file` content and which was still
/// live for `task`.
///
/// Deterministic by construction — the same command always yields the same
/// delimiter — because `recipe-determinism-v1` requires the generated script to
/// be a pure function of the declaration. Extending rather than hashing keeps
/// the common case readable: a command that never mentions the word gets the
/// plain `FORJAR_TIMEOUT` it always had.
pub
/// Run `command` under `timeout` without losing strictness or eating stdin.
///
/// Refs #390-E, and this one silently corrupted results. `timeout N bash <<'D'`
/// gave the nested shell NEITHER the outer `set -euo pipefail` NOR a free stdin:
///
/// * The inner shell exits with the status of its LAST line, so a failing
/// line was swallowed. Measured on the published 1.24.0: a task with
/// `timeout:` and a passing `completion_check` whose command began with
/// `false` reported `1 converged`, while the identical config WITHOUT
/// `timeout:` correctly failed. A wrong result reported as success is worse
/// than a failure.
/// * The nested bash's stdin IS the heredoc, so a command reading stdin
/// consumed the rest of its own script -- the FJ-2732 hole that
/// `transport::stdin_isolation` was written to close, re-opened one layer in.
///
/// Passing the script on fd 3 (`bash /dev/fd/3 3<<'D'`) leaves stdin as the
/// outer shell left it (`/dev/null`, per the transport wrapper) and re-asserting
/// `set -euo pipefail` inside restores the strictness the outer script declared.
pub