Skip to main content

rustev_surface/
replay.rs

1//! Offline `replay` (spec 006, 3.7), for every host.
2
3use rustev_contract::Document;
4use rustev_contract::limits::REPLAY_V1;
5use rustev_contract::replay::ReplayBundle;
6use rustev_eval::replay::{ReplayConfig, ReplayOutcome, reproduce};
7use rustev_eval::resolve::Resolver;
8
9use crate::host::Host;
10use crate::out::{Done, Out, Res, code};
11
12/// Read and parse a bundle file; `None` when it does not exist.
13pub fn read_bundle(
14    command: &str,
15    host: &dyn Host,
16    path: &str,
17) -> Result<Option<ReplayBundle>, Done> {
18    let Some(bytes) = host
19        .read_bounded(path, REPLAY_V1.max_bytes.saturating_add(1))
20        .map_err(|e| Done::io(command, e))?
21    else {
22        return Ok(None);
23    };
24    ReplayBundle::parse(&bytes)
25        .map(Some)
26        .map_err(|e| Done::invalid(command, path, e.to_string()))
27}
28
29/// Offline `replay` of the bundle `path` under the caller's trusted
30/// configuration, resolving external items through `resolver`.
31pub fn replay(
32    command: &str,
33    host: &dyn Host,
34    path: &str,
35    resolver: &dyn Resolver,
36    config: &ReplayConfig,
37) -> Res {
38    let bundle = match read_bundle(command, host, path)? {
39        Some(b) => b,
40        None => return Err(Done::io(command, crate::host::missing(path))),
41    };
42    let out = Out::new(command, "").set("decision_id", &bundle.decision_id);
43    Ok(match reproduce(&bundle, resolver, config) {
44        ReplayOutcome::Reproduced(r) => Done::ok(
45            out.set("status", "reproduced")
46                .set("plan_id", &bundle.plan_id)
47                .set("supplies", &r.supplies().len())
48                .raw(
49                    "judgment",
50                    r.judgment()
51                        .record_canonical()
52                        .expect("a reproduced judgment has a record form"),
53                ),
54        ),
55        ReplayOutcome::Diverged { expected, actual } => {
56            let bytes = |j: &rustev_contract::judgment::Judgment| {
57                j.record_canonical()
58                    .expect("a diverged judgment has a record form")
59            };
60            Done::new(
61                code::DIVERGED,
62                out.set("status", "diverged")
63                    .set("plan_id", &bundle.plan_id)
64                    .raw("expected", bytes(&expected))
65                    .raw("actual", bytes(&actual)),
66            )
67        }
68        ReplayOutcome::Incomparable(i) => Done::new(
69            code::INCOMPARABLE,
70            out.set("status", "incomparable")
71                .set("reason", &i.code())
72                .set("detail", &i.to_string()),
73        ),
74    })
75}