Expand description
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 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);Structs§
- Analysis
Request - What a caller asks a backend to do.
- Analysis
Response - What a backend returns: normalized findings plus the evidence for the run that produced them.
- Ingest
Runner - Consumes a normalized report and yields the same values any other backend would.
- Normalized
Report - A normalized analyzer report — the interchange format
roteiro security ingestconsumes and every analyzer adapter emits. - Report
Finding - One finding as it appears in a normalized report.
- Worktree
- The worktree an analyzer is pointed at.
Enums§
Constants§
- MAX_
REPORT_ FINDINGS - The most findings accepted from one report.
- REPORT_
SCHEMA - Schema tag every normalized report must carry. Bump on a breaking change to
the report format, exactly as
rto_graph::ARTIFACT_SCHEMAdoes for the graph artifact.
Traits§
- Analyzer
Runner - One analyzer backend.
Functions§
- check_
reported_ path - Reject a reported path that is absolute or climbs out of the worktree.
- check_
request - The preflight every backend shares: explicit consent, denied egress, a read-only worktree, and a well-formed analyzer id.
- sha256_
hex - Lowercase hex SHA-256 of
bytes. - worktree_
id - Derive a stable, opaque id for the checkout at
path.