///|
/// Named policy presets let the same manifest be reviewed for different stages
/// of a project: local development, a release candidate, or a strict audit.
pub(all) enum PolicyProfile {
Development
Release
Strict
} derive(Eq, Debug)
///|
pub(all) struct ValidationPolicy {
name : String
minimum_capabilities : Int
minimum_requirements : Int
minimum_evidence : Int
require_out_of_scope : Bool
require_verified_test : Bool
require_verified_example : Bool
require_verified_ci : Bool
require_verified_doc : Bool
require_verified_release : Bool
reject_pending_evidence : Bool
require_should_evidence : Bool
require_unique_targets : Bool
require_lowercase_ids : Bool
require_capability_scope : Bool
require_capability_constraints : Bool
minimum_project_length : Int
minimum_purpose_length : Int
} derive(Eq, Debug)
///|
pub fn PolicyProfile::label(self : PolicyProfile) -> String {
match self {
Development => "development"
Release => "release"
Strict => "strict"
}
}
///|
pub fn development_policy() -> ValidationPolicy {
{
name: "development",
minimum_capabilities: 1,
minimum_requirements: 1,
minimum_evidence: 1,
require_out_of_scope: false,
require_verified_test: true,
require_verified_example: false,
require_verified_ci: false,
require_verified_doc: false,
require_verified_release: false,
reject_pending_evidence: false,
require_should_evidence: false,
require_unique_targets: false,
require_lowercase_ids: false,
require_capability_scope: false,
require_capability_constraints: false,
minimum_project_length: 1,
minimum_purpose_length: 1,
}
}
///|
pub fn release_policy() -> ValidationPolicy {
{
name: "release",
minimum_capabilities: 2,
minimum_requirements: 3,
minimum_evidence: 4,
require_out_of_scope: true,
require_verified_test: true,
require_verified_example: true,
require_verified_ci: true,
require_verified_doc: true,
require_verified_release: false,
reject_pending_evidence: false,
require_should_evidence: true,
require_unique_targets: true,
require_lowercase_ids: true,
require_capability_scope: true,
require_capability_constraints: true,
minimum_project_length: 3,
minimum_purpose_length: 24,
}
}
///|
pub fn strict_policy() -> ValidationPolicy {
{
name: "strict",
minimum_capabilities: 3,
minimum_requirements: 4,
minimum_evidence: 6,
require_out_of_scope: true,
require_verified_test: true,
require_verified_example: true,
require_verified_ci: true,
require_verified_doc: true,
require_verified_release: true,
reject_pending_evidence: true,
require_should_evidence: true,
require_unique_targets: true,
require_lowercase_ids: true,
require_capability_scope: true,
require_capability_constraints: true,
minimum_project_length: 3,
minimum_purpose_length: 40,
}
}
///|
pub fn policy_for(profile : PolicyProfile) -> ValidationPolicy {
match profile {
Development => development_policy()
Release => release_policy()
Strict => strict_policy()
}
}
///|
/// A complete offline fixture for release-policy examples, integration tests,
/// and documentation. It includes distinct artifacts for tests, CI, docs, and
/// a package release, so it also satisfies the strict profile.
pub fn strict_ready_manifest() -> String {
[
"project: CapsuleTrace Release", "purpose: Review release evidence for a deterministic MoonBit traceability library.",
"scope: offline manifest validation", "scope: stable review report generation",
"out-of-scope: network and filesystem access at runtime", "capability: parse | Parse line manifests | text | boundary spec | offline",
"capability: policy | Apply release policy | boundary spec | report | deterministic",
"capability: matrix | Build evidence matrix | boundary spec | matrix | stable order",
"requirement: parser.accepts | must | Parser accepts supported records. | parse",
"requirement: policy.release | must | Policy reports release blockers. | policy",
"requirement: matrix.coverage | should | Matrix shows requirement coverage. | matrix",
"requirement: report.json | may | Reports support structured export. | policy,matrix",
"evidence: test.parser | test | parser_wbtest.mbt | verified | parser.accepts | Parser tests pass.",
"evidence: test.policy | test | policy_wbtest.mbt | verified | policy.release | Policy tests pass.",
"evidence: test.matrix | test | matrix_wbtest.mbt | verified | matrix.coverage | Matrix tests pass.",
"evidence: example.audit | example | cmd/audit | verified | policy.release,matrix.coverage | Runnable audit example.",
"evidence: ci.check | ci | .github/workflows/ci.yml | verified | parser.accepts,policy.release | CI verifies the package.",
"evidence: doc.readme | doc | README.md | verified | matrix.coverage | README documents the API.",
"evidence: release.package | release | mooncakes.io | verified | report.json | Package release is recorded.",
"evidence: design.policy | design | docs/design.md | verified | policy.release | Policy design is documented.",
].join("\n")
}
///|
/// Apply the standard structural checks together with a caller-supplied policy.
/// The policy never mutates the source specification, which makes it safe to run
/// multiple policy profiles against one parsed manifest in CI.
pub fn validate_spec_with_policy(
spec : BoundarySpec,
policy : ValidationPolicy,
) -> TraceReport {
let baseline = validate_spec(spec)
let findings = baseline.findings.copy()
audit_policy_counts(spec, policy, findings)
audit_policy_summary(spec, policy, findings)
audit_policy_capabilities(spec, policy, findings)
audit_policy_requirements(spec, policy, findings)
audit_policy_evidence(spec, policy, findings)
let score = score_findings(findings)
{
score,
grade: grade_score(score),
ready: report_is_ready(findings),
findings,
spec,
}
}
///|
pub fn validate_manifest_with_policy(
raw : StringView,
policy : ValidationPolicy,
) -> TraceReport {
validate_spec_with_policy(parse_manifest(raw), policy)
}
///|
pub fn TraceReport::policy_summary(
self : TraceReport,
policy : ValidationPolicy,
) -> String {
let lines : Array[String] = []
lines.push("Policy: " + policy.name)
lines.push("Ready: " + bool_label(self.ready))
lines.push("Score: " + self.score.to_string() + " (" + self.grade + ")")
lines.push("Critical: " + self.count_by_severity(Critical).to_string())
lines.push("High: " + self.count_by_severity(High).to_string())
lines.push("Medium: " + self.count_by_severity(Medium).to_string())
lines.push("Low: " + self.count_by_severity(Low).to_string())
lines.join("\n")
}
///|
pub fn TraceReport::to_policy_markdown(
self : TraceReport,
policy : ValidationPolicy,
) -> String {
let lines : Array[String] = []
lines.push("# CapsuleTrace Policy Report")
lines.push("")
lines.push("- Policy: " + policy.name)
lines.push("- Project: " + printable(self.spec.project))
lines.push("- Ready: " + bool_label(self.ready))
lines.push("- Score: " + self.score.to_string() + " (" + self.grade + ")")
lines.push("")
lines.push("## Findings")
lines.push("")
lines.push("| Severity | Rule | Subject | Remediation |")
lines.push("| --- | --- | --- | --- |")
if self.findings.is_empty() {
lines.push("| info | clean | all | No policy violations. |")
} else {
for finding in self.findings {
lines.push(
"| " +
finding.severity.label() +
" | " +
markdown_cell(finding.id) +
" | " +
markdown_cell(finding.subject) +
" | " +
markdown_cell(finding.remediation) +
" |",
)
}
}
lines.join("\n")
}
///|
fn audit_policy_counts(
spec : BoundarySpec,
policy : ValidationPolicy,
findings : Array[TraceFinding],
) -> Unit {
audit_minimum(
policy,
"capability-count",
"capability",
spec.capabilities.length(),
policy.minimum_capabilities,
findings,
)
audit_minimum(
policy,
"requirement-count",
"requirement",
spec.requirements.length(),
policy.minimum_requirements,
findings,
)
audit_minimum(
policy,
"evidence-count",
"evidence",
spec.evidence.length(),
policy.minimum_evidence,
findings,
)
}
///|
fn audit_policy_summary(
spec : BoundarySpec,
policy : ValidationPolicy,
findings : Array[TraceFinding],
) -> Unit {
if spec.project.trim().length() < policy.minimum_project_length {
policy_finding(
findings,
policy,
"project-length",
High,
"project",
"project name is shorter than the policy minimum",
"Use a stable project name with at least " +
policy.minimum_project_length.to_string() +
" characters.",
)
}
if spec.purpose.trim().length() < policy.minimum_purpose_length {
policy_finding(
findings,
policy,
"purpose-length",
Medium,
"purpose",
"project purpose is too short to be reviewable",
"Describe the user problem and intended boundary in at least " +
policy.minimum_purpose_length.to_string() +
" characters.",
)
}
if policy.require_out_of_scope && spec.out_of_scope.is_empty() {
policy_finding(
findings,
policy,
"out-of-scope",
Medium,
"out-of-scope",
"policy requires at least one explicit non-goal",
"Add an out-of-scope record for functionality the package will not provide.",
)
}
}
///|
fn audit_policy_capabilities(
spec : BoundarySpec,
policy : ValidationPolicy,
findings : Array[TraceFinding],
) -> Unit {
for capability in spec.capabilities {
if policy.require_lowercase_ids && !is_lowercase_identifier(capability.id) {
policy_finding(
findings,
policy,
"capability-id-style",
Low,
capability.id,
"capability id must use lowercase identifier style",
"Use lowercase ids such as parse-manifest or report.export.",
)
}
if policy.require_capability_scope &&
spec.requirements_for_capability(capability.id).is_empty() {
policy_finding(
findings,
policy,
"capability-requirement",
Medium,
capability.id,
"capability is not linked to an observable requirement",
"Add a requirement that references this capability.",
)
}
if policy.require_capability_constraints &&
capability.constraints.is_empty() {
policy_finding(
findings,
policy,
"capability-constraints",
Medium,
capability.id,
"capability has no documented constraints",
"Document determinism, offline limits, performance goals, or other boundaries.",
)
}
}
}
///|
fn audit_policy_requirements(
spec : BoundarySpec,
policy : ValidationPolicy,
findings : Array[TraceFinding],
) -> Unit {
for requirement in spec.requirements {
if policy.require_lowercase_ids && !is_lowercase_identifier(requirement.id) {
policy_finding(
findings,
policy,
"requirement-id-style",
Low,
requirement.id,
"requirement id must use lowercase identifier style",
"Use lowercase ids such as parser.accepts-records or export.json.",
)
}
if policy.require_should_evidence &&
requirement.level == Should &&
spec.evidence_for_requirement(requirement.id).is_empty() {
policy_finding(
findings,
policy,
"should-evidence",
Medium,
requirement.id,
"should requirement has no evidence under this policy",
"Attach test, example, documentation, CI, or design evidence.",
)
}
}
}
///|
fn audit_policy_evidence(
spec : BoundarySpec,
policy : ValidationPolicy,
findings : Array[TraceFinding],
) -> Unit {
let seen_targets : Map[String, String] = Map([])
let mut has_verified_test = false
let mut has_verified_example = false
let mut has_verified_ci = false
let mut has_verified_doc = false
let mut has_verified_release = false
for item in spec.evidence {
if policy.require_lowercase_ids && !is_lowercase_identifier(item.id) {
policy_finding(
findings,
policy,
"evidence-id-style",
Low,
item.id,
"evidence id must use lowercase identifier style",
"Use lowercase ids such as test.parser or ci.check.",
)
}
if item.status == Verified {
match item.kind {
Test => has_verified_test = true
Example => has_verified_example = true
CI => has_verified_ci = true
Doc => has_verified_doc = true
Release => has_verified_release = true
Design => ()
}
}
if policy.reject_pending_evidence && item.status == Pending {
policy_finding(
findings,
policy,
"pending-evidence",
Medium,
item.id,
"pending evidence is not allowed by this policy",
"Verify the artifact or remove it from the release manifest.",
)
}
if policy.require_unique_targets && !item.target.trim().is_empty() {
let target_key = item.target.trim().to_lower().to_owned()
match seen_targets.get(target_key) {
Some(first_id) =>
policy_finding(
findings,
policy,
"duplicate-evidence-target",
Low,
item.id,
"evidence target is already claimed by " + first_id,
"Use one evidence record per target or document why the records differ.",
)
None => seen_targets.set(target_key, item.id)
}
}
}
audit_required_evidence(
findings,
policy,
"verified-test",
"test",
has_verified_test,
policy.require_verified_test,
)
audit_required_evidence(
findings,
policy,
"verified-example",
"example",
has_verified_example,
policy.require_verified_example,
)
audit_required_evidence(
findings,
policy,
"verified-ci",
"ci",
has_verified_ci,
policy.require_verified_ci,
)
audit_required_evidence(
findings,
policy,
"verified-doc",
"documentation",
has_verified_doc,
policy.require_verified_doc,
)
audit_required_evidence(
findings,
policy,
"verified-release",
"release",
has_verified_release,
policy.require_verified_release,
)
}
///|
fn audit_minimum(
policy : ValidationPolicy,
rule : String,
subject : String,
actual : Int,
expected : Int,
findings : Array[TraceFinding],
) -> Unit {
if actual < expected {
policy_finding(
findings,
policy,
rule,
Medium,
subject,
"policy requires at least " +
expected.to_string() +
", found " +
actual.to_string(),
"Add independent, reviewable " + subject + " records before release.",
)
}
}
///|
fn audit_required_evidence(
findings : Array[TraceFinding],
policy : ValidationPolicy,
rule : String,
subject : String,
actual : Bool,
required : Bool,
) -> Unit {
if required && !actual {
policy_finding(
findings,
policy,
rule,
High,
subject,
"policy requires verified " + subject + " evidence",
"Add a verified " + subject + " evidence record tied to a requirement.",
)
}
}
///|
fn policy_finding(
findings : Array[TraceFinding],
policy : ValidationPolicy,
rule : String,
severity : Severity,
subject : String,
message : String,
remediation : String,
) -> Unit {
findings.push({
id: "policy." + policy.name + "." + rule,
severity,
subject,
message,
remediation,
})
}
///|
fn is_lowercase_identifier(value : String) -> Bool {
let trimmed = value.trim().to_owned()
trimmed == trimmed.to_lower() && looks_like_id(trimmed)
}