///|
pub(all) struct ReleaseEvidence {
applicant_name : String
github_owner : String
repository_name : String
mooncakes_package : String
version : String
ci_status : String
issue_records : Array[String]
test_records : Array[String]
release_notes : String
design_notes : String
license : String
} derive(Eq, Debug)
///|
pub fn ReleaseEvidence::ReleaseEvidence(
applicant_name? : String = "",
github_owner? : String = "",
repository_name? : String = "",
mooncakes_package? : String = "",
version? : String = "",
ci_status? : String = "",
issue_records? : Array[String] = [],
test_records? : Array[String] = [],
release_notes? : String = "",
design_notes? : String = "",
license? : String = "",
) -> ReleaseEvidence {
{
applicant_name,
github_owner,
repository_name,
mooncakes_package,
version,
ci_status,
issue_records,
test_records,
release_notes,
design_notes,
license,
}
}
///|
pub(all) struct EvidenceReport {
score : Int
verdict : String
findings : Array[RolloutFinding]
} derive(Eq, Debug)
///|
pub fn release_evidence_review(evidence : ReleaseEvidence) -> EvidenceReport {
let findings = Array::new(capacity=12)
push_evidence_identity(findings, evidence)
push_evidence_ci(findings, evidence)
push_evidence_records(findings, evidence)
push_evidence_docs(findings, evidence)
let score = evidence_score(findings)
{ score, verdict: evidence_verdict(findings, score), findings }
}
///|
fn push_evidence_identity(
findings : Array[RolloutFinding],
evidence : ReleaseEvidence,
) -> Unit {
if evidence.applicant_name == "张雨晴" {
findings.push(
finding_pass(
"evidence.applicant",
IdentityGate,
"Applicant identity is fixed",
"张雨晴",
),
)
} else {
findings.push(
finding_fail(
"evidence.applicant",
IdentityGate,
"Applicant identity is not aligned",
evidence.applicant_name,
"Use the applicant name from the official form.",
penalty=18,
),
)
}
if evidence.github_owner == "EJJ-ai-nb" &&
evidence.repository_name == "canaryring" {
findings.push(
finding_pass(
"evidence.github",
IdentityGate,
"GitHub owner and repository are aligned",
"EJJ-ai-nb/canaryring",
),
)
} else {
findings.push(
finding_fail(
"evidence.github",
IdentityGate,
"GitHub owner or repository is not aligned",
evidence.github_owner + "/" + evidence.repository_name,
"Use EJJ-ai-nb/canaryring for the new non-overlapping project.",
penalty=18,
),
)
}
if evidence.mooncakes_package == "EJJ-ai-nb/canaryring" {
findings.push(
finding_pass(
"evidence.mooncakes",
IdentityGate,
"Mooncakes package is aligned",
evidence.mooncakes_package,
),
)
} else {
findings.push(
finding_fail(
"evidence.mooncakes",
IdentityGate,
"Mooncakes package is not aligned",
evidence.mooncakes_package,
"Publish as EJJ-ai-nb/canaryring.",
penalty=18,
),
)
}
}
///|
fn push_evidence_ci(
findings : Array[RolloutFinding],
evidence : ReleaseEvidence,
) -> Unit {
let status = lower(evidence.ci_status)
if status == "success" || status == "passed" {
findings.push(
finding_pass(
"evidence.ci",
MetricGate,
"CI status is successful",
evidence.ci_status,
),
)
} else if status == "" {
findings.push(
finding_warn(
"evidence.ci",
MetricGate,
"CI status is not recorded",
"unknown",
"Record the latest main-branch GitHub Actions result.",
penalty=8,
),
)
} else {
findings.push(
finding_fail(
"evidence.ci",
MetricGate,
"CI status is not successful",
evidence.ci_status,
"Fix CI before submitting or accepting the project.",
penalty=20,
),
)
}
}
///|
fn push_evidence_records(
findings : Array[RolloutFinding],
evidence : ReleaseEvidence,
) -> Unit {
if evidence.issue_records.length() >= 1 {
findings.push(
finding_pass(
"evidence.issues",
PlanShape,
"Issue or work records are retained",
evidence.issue_records.length().to_string() + " records",
),
)
} else {
findings.push(
finding_warn(
"evidence.issues",
PlanShape,
"Issue or work records are missing",
"0",
"Keep at least one work item record for the new project.",
penalty=6,
),
)
}
if evidence.test_records.length() >= 1 {
findings.push(
finding_pass(
"evidence.tests",
PlanShape,
"Test records are retained",
evidence.test_records.length().to_string() + " records",
),
)
} else {
findings.push(
finding_fail(
"evidence.tests",
PlanShape,
"Test records are missing",
"0",
"Record local and CI test results.",
penalty=15,
),
)
}
}
///|
fn push_evidence_docs(
findings : Array[RolloutFinding],
evidence : ReleaseEvidence,
) -> Unit {
if has_text(evidence.release_notes) {
findings.push(
finding_pass(
"evidence.release-notes",
PlanShape,
"Release notes are retained",
"present",
),
)
} else {
findings.push(
finding_warn(
"evidence.release-notes",
PlanShape,
"Release notes are missing",
"empty",
"Add CHANGELOG.md or release notes.",
penalty=5,
),
)
}
if has_text(evidence.design_notes) {
findings.push(
finding_pass(
"evidence.design",
PlanShape,
"Design notes are retained",
"present",
),
)
} else {
findings.push(
finding_warn(
"evidence.design",
PlanShape,
"Design notes are missing",
"empty",
"Add a design boundary note.",
penalty=5,
),
)
}
if evidence.license == "MIT" || evidence.license == "Apache-2.0" {
findings.push(
finding_pass(
"evidence.license",
PlanShape,
"Open-source license is clear",
evidence.license,
),
)
} else {
findings.push(
finding_fail(
"evidence.license",
PlanShape,
"Open-source license is unclear",
evidence.license,
"Use an OSI-recognized license and keep LICENSE at repository root.",
penalty=20,
),
)
}
}
///|
fn evidence_score(findings : Array[RolloutFinding]) -> Int {
let mut score = 100
for finding in findings {
score -= finding.penalty
}
clamp(score, 0, 100)
}
///|
fn evidence_verdict(findings : Array[RolloutFinding], score : Int) -> String {
let mut fails = 0
for finding in findings {
if finding.severity == Fail {
fails += 1
}
}
if fails > 0 {
"blocked"
} else if score >= 90 {
"ready"
} else {
"needs-notes"
}
}
///|
pub fn EvidenceReport::to_markdown(self : EvidenceReport) -> String {
let out = StringBuilder()
out.write_string("# CanaryRing Release Evidence\n\n")
out.write_string("- Verdict: " + self.verdict + "\n")
out.write_string("- Score: " + self.score.to_string() + "/100\n\n")
for finding in self.findings {
out.write_string("- [" + severity_text(finding.severity) + "] ")
out.write_string(finding.id + ": " + finding.title)
if finding.evidence != "" {
out.write_string(" -- " + finding.evidence)
}
out.write_string("\n")
}
out.to_string()
}
///|
pub fn example_release_evidence() -> ReleaseEvidence {
ReleaseEvidence(
applicant_name="张雨晴",
github_owner="EJJ-ai-nb",
repository_name="canaryring",
mooncakes_package="EJJ-ai-nb/canaryring",
version="0.1.0",
ci_status="success",
issue_records=["scope: deterministic canary rollout planner"],
test_records=["moon test --deny-warn"],
release_notes="0.1.0 initial canary rollout simulator",
design_notes="Pure MoonBit, no network or shell access in core library",
license="MIT",
)
}