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
//! The analyzer execution seam: one contract, interchangeable backends.
//!
//! Running an external analyzer (`cargo-audit`, `semgrep`, successors) can happen
//! in CI, on a developer's machine, or — later — locally inside a sandbox. This
//! crate exists so those stop being competing architectures: every backend
//! implements one [`AnalyzerRunner`] trait, takes one [`AnalysisRequest`], and
//! returns one [`AnalysisResponse`] of normalized findings plus run evidence. A
//! caller never learns which backend produced a result, so adding the sandboxed
//! and subprocess backends later changes no call site.
//!
//! Today there is exactly one implementation, [`IngestRunner`], which consumes a
//! normalized report produced elsewhere. It is the zero-install default, not a
//! fallback: it needs no container runtime and adds no isolation surface, and
//! what it produces is byte-for-byte the shape a sandboxed run will produce.
//!
//! # What this crate does not do
//!
//! It does not *always* produce something to store, either. [`lint`] runs a
//! linter and returns a report the caller prints: no [`AnalysisRun`], no layer,
//! no store. A lint name is a symbol in a compiler rather than an assigned
//! identifier, so it is an opinion about the code as it stands today rather than
//! a durable fact about the repository (ADR-0020 v1.1) — and everything below
//! about persistence simply does not apply to it.
//!
//! [`AnalysisRun`]: rto_graph::AnalysisRun
//! [`lint`]: crate::lint
//!
//! It does not decide how results are *stored*. Persistence lives in `rto-graph`,
//! which files findings in their own tables — never `nodes`/`edges`, never a
//! provenance class, never in the exported graph artifact (ADR-0012). Nothing
//! here can move the published `GraphArtifact` by a byte, and that is checked by
//! test rather than assumed.
//!
//! No analyzer is implemented here, and no sandbox dependency is pulled in; the
//! backends arrive behind their own features (ADR-0014).
//!
//! @rto:0014
//! @rto:0012
//!
//! # Example
//!
//! ```
//! use rto_exec::{AnalysisRequest, AnalyzerRunner, Consent, IngestRunner, Worktree};
//! use rto_graph::SourceIdentity;
//!
//! let report = br#"{
//! "schema": "roteiro.findings/v1",
//! "analyzer": "cargo-audit",
//! "analyzer_version": "0.21.0",
//! "started_at": "2026-08-15T09:00:00Z",
//! "ended_at": "2026-08-15T09:00:04Z",
//! "exit_status": 1,
//! "findings": [{
//! "identity": ["RUSTSEC-2024-0001", "openssl", "0.10.5", "lock123"],
//! "rule": "RUSTSEC-2024-0001",
//! "severity": "high",
//! "title": "openssl is vulnerable",
//! "message": "upgrade to 0.10.66"
//! }]
//! }"#;
//!
//! let request = AnalysisRequest {
//! analyzer: "cargo-audit".to_owned(),
//! worktree: Worktree::read_only("/repo".as_ref()).expect("worktree"),
//! network: rto_graph::NetworkPolicy::Deny,
//! consent: Consent::Granted,
//! source: SourceIdentity::default(),
//! };
//! let response = IngestRunner::new(report.to_vec()).run(&request).expect("ingest");
//! assert_eq!(response.findings.len(), 1);
//! assert_eq!(response.run.isolation, rto_graph::Isolation::Ingested);
//! ```
/// Where the pinned-asset cache lives, and the precedence that decides it.
///
/// Its source carries no `//!` header because `build.rs` pulls the same file in
/// with `include!`, where an inner doc comment is a syntax error — so the module
/// documentation lives here instead. `build.rs` needs it to find the sandbox
/// runtime `roteiro security prefetch` installed, which is the same cache
/// [`asset_paths::asset_root`] names; read the file's own comments for why that
/// is shared rather than copied.
// Asset provisioning is **always compiled**, behind no feature at all.
//
// It used to be `cfg(any(exec-subprocess, exec-boxlite))`, on the reading that
// provisioning belongs to whichever backend consumes the assets. That was the
// wrong shape and this module half-said so already: it is shared between the
// backends and owned by neither, and the note on `SANDBOX_RUNTIME_NOTICE` below
// records that an `exec-subprocess`-only build provisions *for a later
// `exec-boxlite` build* — provisioning already served a backend that was not
// compiled in.
//
// The bootstrap argument settles it. `AGENTS.md` tells a contributor to run
// `roteiro security prefetch --allow-download` *before* building
// `--features exec-boxlite`, because that build script requires the verified
// archive at compile time. If prefetch lived behind an execution feature, you
// would need a build with a *different* execution backend compiled in before you
// could provision the one you actually wanted. That is circular.
//
// Nothing here executes anything: it downloads, digests, pins and reports. Every
// `Command::new` in this crate is in `subprocess.rs` or `boxlite.rs`, and both
// stay behind their features. Provisioning is not execution.
/// What an analyzer's environment is — for **both** backends, in one place.
///
/// Private because it is a seam between this crate's backends rather than a
/// contract with a caller. It exists as its own module because it used to exist
/// as two: a `ChildEnv` in [`subprocess`] and a hand-rolled list in [`boxlite`],
/// which is how `CARGO_TARGET_DIR` came to be listed as a name to *inherit*
/// under a promise that it was *set*. Read the module for why a guest has no
/// `inherit` half at all.
/// Emitting a `file://` URL for a local path, and reading one back.
///
/// Its source carries no `//!` header because `build.rs` pulls the same file in
/// with `include!`, where an inner doc comment is a syntax error — so the module
/// documentation lives here instead. `build.rs` is this crate's only emitter: it
/// prints the `BOXLITE_RUNTIME_URL=` recipe an operator pastes, and parses that
/// variable back when it is set. What reads the URL in between is `boxlite`'s
/// own `curl`, which percent-decodes and rejects an unencoded space outright —
/// read the file's own comments for the measurements, and for why the encoder
/// and the decoder have to be one file rather than two.
/// How a refusal is written, so that a way forward stays one.
///
/// Ungated, like [`lint_grant`], and for the same kind of reason: what a refusal
/// owes its reader is not a property of which backends were compiled in. Read
/// the module for the failure it makes unrepresentable — three of this crate's
/// refusals leaked source indentation into shipped output at once, which says
/// the way they were written invited it.
/// Running a linter and **reporting** it, with no store anywhere in the path.
///
/// The other half of this crate produces artifacts; this module deliberately
/// does not (ADR-0020 v1.1). It has no [`AnalyzerRunner`] implementation, takes
/// no [`Consent`], and cannot reach [`rto_graph::Store`] — read its own
/// documentation for why a lint is not a finding, and why relaxing
/// [`check_request`] to fit a builder through the reader-class preflight is the
/// conversion ADR-0014 warns against rather than a refactor.
/// ADR-0020 §6's grant: may a linter run on **this host**?
///
/// Ungated, unlike [`lint`] itself. A policy that existed only where the
/// capability does would be the conversion ADR-0014 warns about, so the answer
/// is the same in a build that cannot run a linter as in one that can — see the
/// module's own documentation.
/// ADR-0020 conditions 1-2: the **sandboxed builder** — `roteiro lint`'s default.
///
/// The boundary half of [`lint`]. It adds one writable mount to what
/// [`boxlite`] already does and removes nothing: the worktree stays read-only,
/// [`check_request`]'s preflight is untouched, and the package cache is a
/// read-only mount of this machine's own rather than a vendored copy. Read its
/// documentation for why the image is supplied rather than pinned here, and why
/// a `$CARGO_HOME` root is not what gets mounted.
///
/// Gated on **both** backends. The boundary does not imply the escape hatch —
/// `exec-boxlite` still does not enable `exec-subprocess`, and enabling one must
/// never switch on the other. This module needs both because it shares
/// [`lint`]'s report shape and its one host-side `cargo locate-project`, which
/// is how it learns what to mount.
/// The per-file digests of the extracted sandbox runtime — **generated**.
///
/// Derived from the archives in [`runtime_pins`] by
/// `scripts/derive-runtime-file-pins.py`, and verified by `build.rs` against
/// what `boxlite` actually extracted, since those files rather than the archive
/// are what `include_bytes!` puts in the binary. Same `include!` arrangement,
/// and so the same standalone constraint; its module documentation lives here
/// for the same reason [`runtime_pins`]'s does.
/// The pinned sandbox-runtime archives, and the host-platform selection.
///
/// Its source carries no `//!` header because `build.rs` pulls the same file in
/// with `include!`, where an inner doc comment is a syntax error — so the module
/// documentation lives here instead. Read the file's own comments for what is
/// pinned and why it has to be.
/// The **sandbox image store**: what it is holding, and dropping it safely.
///
/// Ungated, like [`assets`], and the argument is the same one that moved
/// provisioning off the backend features: reclaiming the bytes a previous build
/// cached must not require rebuilding with the backend that cached them. Nothing
/// here executes anything — it reads an index, measures files, and removes what
/// a pinned digest re-obtains (ADR-0014 v1.6).
/// The **read-only documents** `security list` / `security status` return over a
/// model-facing tool surface.
///
/// Ungated, like [`guidance`] and [`lint_grant`], and for a related reason: what
/// a read owes its reader is not a property of which backends were compiled in.
/// It is also the one place either document is built — the CLI's `security
/// status` shares its coverage matrix and staleness rows from here, so
/// `possibly_stale` and `ready` are one computation rather than three. Read the
/// module for the two hazards it exists to remove: an empty listing that reads as
/// a clean one, and a status blob whose two halves have different scopes.
pub use ;
// The linter's adapter is re-exported like any other, and — unlike any other —
// is **not** in [`ADAPTERS`], so `ingest` cannot resolve it and nothing can file
// its output as a layer. See [`adapter::clippy`].
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
// ADR-0020 §6's grant. Re-exported under `lint_`-prefixed names because the
// concepts have twins in `rto-remote` (ADR-0019 §3) and a reader who meets
// `ConfigGrant` in the binary must be able to see which of the two it is.
pub use ;
pub use BuilderError as LintBuilderError;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
/// The licence notice for the third-party binaries an `exec-boxlite` build
/// embeds, compiled in so it cannot be separated from what it describes.
///
/// `roteiro security prefetch` prints it before installing the sandbox runtime,
/// which is the same disclose-then-consent shape `roteiro model pull` uses. It
/// is compiled into every build, because every build can provision the runtime —
/// including one with no execution backend at all, which prefetches it for a
/// later `exec-boxlite` build — so the obligations travel with the artifact
/// rather than living only in the repository.
pub const SANDBOX_RUNTIME_NOTICE: &str = include_str!;
/// Lowercase hex SHA-256 of `bytes`.
///
/// Used for the report digest that ties an `AnalysisRun` to the exact bytes it
/// was derived from, and for deriving an opaque worktree id from a path.