1use 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
12pub 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
29pub 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}