///|
fn plan_change_name(kind : PlanChangeKind) -> String {
match kind {
RunMetadataChanged => "run_metadata_changed"
ReportAdded => "report_added"
ReportRemoved => "report_removed"
ReportBindingChanged => "report_binding_changed"
CaseAdded => "case_added"
CaseRemoved => "case_removed"
CaseOrderChanged => "case_order_changed"
}
}
///|
fn review_fields(
before : @ledger.RunPlan,
proposed : @ledger.RunPlan,
changes : Array[PlanChange],
) -> Map[String, Json] {
{
"schema": Json::number(1),
"kind": Json::string("moontestledger/plan-review"),
"before": plan_json(before),
"proposed": plan_json(proposed),
"before_sha256": Json::string(sha256_text(encode_plan(before))),
"proposed_sha256": Json::string(sha256_text(encode_plan(proposed))),
"changes": Json::array(
changes.map(change => {
Json::object({
"kind": Json::string(plan_change_name(change.kind)),
"report_id": Json::string(change.report_id),
"key": match change.key {
None => Json::null()
Some(key) => key_json(key)
},
})
}),
),
}
}
///|
/// Full plans and deterministic changes are retained. Over-budget reviews are
/// refused, never shortened. Digests use the existing canonical plan encoding.
pub fn PlanReview::encode(
self : PlanReview,
limits~ : @ledger.Limits = @ledger.Limits::default(),
) -> String raise @ledger.InputError {
ignore(
@ledger.RunPlan::new(
self.before.run_id,
self.before.revision,
self.before.reports,
limits~,
),
)
ignore(
@ledger.RunPlan::new(
self.proposed.run_id,
self.proposed.revision,
self.proposed.reports,
limits~,
),
)
let fields = review_fields(self.before, self.proposed, self.changes)
fields["review_sha256"] = Json::string(self.token)
let source = Json::object(fields).stringify(indent=2)
if source.length() > limits.max_source_units {
raise bad(
"ReviewLimit", "/", "Complete review exceeds the source/output budget; increase an allowed limit, never truncate the inventory or changes",
)
}
source
}
///|
/// Strictly decode then recompute both plan hashes, every change and the token.
/// Duplicate/unknown fields and any supplied projection mismatch are refused.
pub fn decode_plan_review(
source : String,
limits~ : @ledger.Limits = @ledger.Limits::default(),
) -> PlanReview raise @ledger.InputError {
let fields = object(strict_json(source, limits), "$")
keys(
fields,
[
"schema", "kind", "before", "proposed", "before_sha256", "proposed_sha256",
"changes", "review_sha256",
],
"$",
)
schema(fields)
if string_field(fields, "kind", "$") != "moontestledger/plan-review" {
raise bad(
"ReviewKind", "$/kind", "Expected a complete inventory plan review",
)
}
let before = decode_plan(required(fields, "before", "$").stringify(), limits~)
let proposed = decode_plan(
required(fields, "proposed", "$").stringify(),
limits~,
)
let review = review_plan(before, proposed, limits~)
let expected = review_fields(review.before, review.proposed, review.changes)
expected["review_sha256"] = Json::string(review.token)
if Json::object(fields) != Json::object(expected) {
raise bad(
"ReviewMismatch", "$", "Review hashes, complete declarations or changes differ from recomputed inventory; regenerate and review rather than editing the approval token",
)
}
review
}