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
//! `task::executor` — [`ForgeExecutor`] trait + execution context.
//!
//! A `ForgeExecutor` turns a [`ForgeSpec`] into a terminal-status execution.
//! Implementations:
//!
//! - [`crate::local::LocalForgeDriver`] — host subprocess (native or
//! container). Handles [`ForgeCommand::Subprocess`]; rejects `BuildImage`
//! and `Workload` with [`ForgeExecutorError::Unsupported`].
//! - [`crate::remote::RemoteForgeDriver`] — yubaba RPC. Today exposes its
//! own `start()` shape; a `ForgeExecutor` impl is a follow-up when a
//! consumer needs to dispatch uniformly through `dyn ForgeExecutor`.
//!
//! `ForgeSpec` already says *what* to run and *where/how* to sandbox it.
//! [`ExecContext`] carries host-side execution detail (cwd, env) that the
//! wire-format spec deliberately omits — keep `ForgeSpec` portable across
//! the yubaba/cloud boundary; let drivers receive context out-of-band.
use PathBuf;
use async_trait;
use Error;
use UnboundedSender;
use ;
/// Host-side execution context for a forge run.
///
/// Empty by default. Driver semantics:
/// - **Native**: `cwd = None` inherits the caller's working directory; `env`
/// is merged on top of the parent environment.
/// - **Container**: `cwd` becomes the bind-mounted host path *and* the
/// container `WORKDIR` (mirroring [`crate::local::local_container_command`]);
/// `env` becomes `-e KEY=VAL` arguments — container env is otherwise empty
/// beyond what the image declares; `platform`, when set, emits `--platform
/// <value>` so a single-arch upstream image can run under host emulation
/// (e.g. Apple Silicon hosts emulating `linux/amd64` via Rosetta).
/// One streaming event from a running forge.
///
/// Events arrive in causal order on a single sink: exactly one [`Started`],
/// any number of [`Output`], then exactly one [`Finished`]. After
/// [`Finished`] no further events are emitted on this sink.
///
/// [`Started`]: ExecEvent::Started
/// [`Output`]: ExecEvent::Output
/// [`Finished`]: ExecEvent::Finished
/// Which of the two process output streams a line came from.
/// Terminal outcome of a forge run plus a captured stderr tail.
///
/// `stderr_tail` is the trailing stderr lines collected by the driver
/// (joined by `\n`, trimmed) for callers that want to surface a failure
/// message without re-aggregating from the event sink. The driver populates
/// it whether or not the sink is attached.
/// Driver that runs a [`ForgeSpec`] to terminal status.
///
/// `sink` is optional: pass `None` for fire-and-forget execution where only
/// the outcome matters (cloud reconciler materialize step); pass a sink for
/// live-stream forwarding (qed runner adapts each [`ExecEvent`] into a
/// `QedEvent::StepOutput`).