1use kernel::event::record::PromptShapeCompared;
10use kernel::{AxCode, AxError};
11use serde::Deserialize;
12
13pub fn rebuild_shape(
28 previous: Option<(&serde_json::Value, &serde_json::Value)>,
29 assembled: &serde_json::Value,
30 compared: &serde_json::Value,
31) -> Result<(), AxError> {
32 let (rebuilt, recorded) = rebuild_one(assembled, compared)?;
33 let prior = match previous {
34 Some((prior_assembled, prior_compared)) => {
35 Some(rebuild_one(prior_assembled, prior_compared)?.0)
36 }
37 None => None,
38 };
39 if rebuilt.attribute(prior.as_ref()) != recorded.changed {
40 return Err(shape_line_disagrees(
41 "its attribution names regions that did not move",
42 ));
43 }
44 let bound = rebuilt.request_upper_bound()?;
45 if bound != recorded.upper_bound {
46 return Err(shape_line_disagrees(
47 "its billing bound is not what its own parts add up to",
48 ));
49 }
50 Ok(())
51}
52
53fn rebuild_one(
56 assembled: &serde_json::Value,
57 compared: &serde_json::Value,
58) -> Result<(crate::prefix::shape::PromptShape, PromptShapeCompared), AxError> {
59 let recorded = PromptShapeCompared::deserialize(compared).map_err(|err| {
60 AxError::failure(
61 AxCode::CasCorrupt,
62 "rebuild the prompt shape",
63 format!("a prompt_shape_compared payload does not parse: {err}"),
64 )
65 .with_recovery(
66 "replay with the build that wrote this ledger, or report the damaged line: its \
67 prompt_shape_compared payload is not what this build reads",
68 )
69 })?;
70 let parts = crate::prefix::shape::segment_parts_of(assembled)?;
71 Ok((
72 crate::prefix::shape::PromptShape::from_parts(parts, recorded.tools, recorded.run),
73 recorded,
74 ))
75}
76
77fn shape_line_disagrees(why: &str) -> AxError {
78 AxError::failure(
79 AxCode::CasCorrupt,
80 "rebuild the prompt shape",
81 format!("a prompt_shape_compared line disagrees with its own records: {why}"),
82 )
83 .with_recovery(
84 "replay with the build that wrote this ledger, or report the damaged line: its recorded \
85 facts do not add up to its recorded judgement",
86 )
87}