///|
pub(all) struct AcceptanceCriterionReview {
criterion : String
satisfied : Bool
evidence_refs : Array[String]
note : String
} derive(Debug, Eq, ToJson)
///|
pub(all) struct AcceptanceReviewReceipt {
contract_id : String
review_id : String
run_id : String
work_item_id : String
declaration_id : String
result_id : String
attempt_id : String
output_digest : String
decision : String
criteria : Array[AcceptanceCriterionReview]
reviewer_id : String
review_authority_id : String
receipt_artifact : String
recorded_at : String
} derive(Debug, Eq, ToJson)
///|
pub(all) suberror AcceptanceReviewError {
InvalidAcceptanceReview(Array[String])
AcceptanceReviewItemUnavailable(String)
AcceptanceReviewIdentityMismatch(
field~ : String,
expected~ : String,
actual~ : String
)
AcceptanceReviewConflict(String)
} derive(Debug, Eq, ToJson)
///|
fn decode_criterion_review(value : Json) -> AcceptanceCriterionReview raise {
let fields = wire_object(value, "acceptance criterion review")
{
criterion: wire_string(fields, "criterion"),
satisfied: wire_bool(fields, "satisfied"),
evidence_refs: wire_strings(fields, "evidence_refs", required=false),
note: wire_string(fields, "note"),
}
}
///|
pub fn decode_acceptance_review_receipt(
value : Json,
) -> AcceptanceReviewReceipt raise {
let fields = wire_object(value, "acceptance review receipt")
guard fields.get("criteria") is Some(Array(criteria)) else {
fail("acceptance review receipt requires criteria array")
}
{
contract_id: wire_string(fields, "contract_id"),
review_id: wire_string(fields, "review_id"),
run_id: wire_string(fields, "run_id"),
work_item_id: wire_string(fields, "work_item_id"),
declaration_id: wire_string(fields, "declaration_id"),
result_id: wire_string(fields, "result_id"),
attempt_id: wire_string(fields, "attempt_id"),
output_digest: wire_string(fields, "output_digest"),
decision: wire_string(fields, "decision"),
criteria: criteria.map(value => decode_criterion_review(value)),
reviewer_id: wire_string(fields, "reviewer_id"),
review_authority_id: wire_string(fields, "review_authority_id"),
receipt_artifact: wire_string(fields, "receipt_artifact"),
recorded_at: wire_string(fields, "recorded_at"),
}
}
///|
pub fn AcceptanceReviewReceipt::quality_issues(
self : AcceptanceReviewReceipt,
) -> Array[String] {
let issues : Array[String] = []
if self.contract_id != "moonflow.acceptance-review.v1" {
issues.push("unsupported acceptance review contract")
}
if self.decision != "accepted" && self.decision != "rejected" {
issues.push("decision must be accepted or rejected")
}
if self.criteria.is_empty() {
issues.push("criterion reviews are required")
}
let seen : Array[String] = []
for review in self.criteria {
if review.criterion.trim().is_empty() {
issues.push("review criterion is required")
} else if seen.contains(review.criterion) {
issues.push("duplicate reviewed criterion: \{review.criterion}")
} else {
seen.push(review.criterion)
}
if review.note.trim().is_empty() {
issues.push("criterion review note is required")
}
if review.satisfied && review.evidence_refs.is_empty() {
issues.push("satisfied criterion requires evidence: \{review.criterion}")
}
}
if self.decision == "accepted" &&
self.criteria.any(review => !review.satisfied) {
issues.push("accepted review requires every criterion to be satisfied")
}
if self.decision == "rejected" &&
self.criteria.all(review => review.satisfied) {
issues.push("rejected review requires an unsatisfied criterion")
}
if !artifact_ref_is_workspace_relative(self.receipt_artifact) {
issues.push("acceptance receipt must be a workspace-relative artifact")
}
issues
}
///|
fn require_review_identity(
field : String,
expected : String,
actual : String,
) -> Unit raise AcceptanceReviewError {
if expected != actual {
raise AcceptanceReviewIdentityMismatch(field~, expected~, actual~)
}
}
///|
pub fn acceptance_review_event(
projection : RunProjection,
receipt : AcceptanceReviewReceipt,
) -> RuntimeEvent raise AcceptanceReviewError {
let issues = receipt.quality_issues()
if !issues.is_empty() {
raise InvalidAcceptanceReview(issues)
}
guard projection.find_item(receipt.work_item_id) is Some(item) else {
raise AcceptanceReviewItemUnavailable(receipt.work_item_id)
}
require_review_identity("run_id", projection.run_id, receipt.run_id)
require_review_identity(
"declaration_id",
item.declaration_id,
receipt.declaration_id,
)
require_review_identity("result_id", item.result_id, receipt.result_id)
require_review_identity(
"attempt_id",
item.active_attempt_id,
receipt.attempt_id,
)
require_review_identity(
"output_digest",
item.output_digest,
receipt.output_digest,
)
if receipt.criteria.length() != item.acceptance_criteria.length() {
raise InvalidAcceptanceReview([
"criterion review count must match the declaration",
])
}
for index in 0..