moonmodguard

A MoonBit project manifest and supply-chain policy auditor.

moonbit
audit
manifest
supply-chain
policy
moon add Noverberrain/moonmodguard@0.3.0
Download zip
Version
0.3.0
License
Apache-2.0
Last updated
4 hours ago
Downloads
16

Dependencies

README

#MoonModGuard

MoonModGuard is a MoonBit project manifest and supply-chain policy auditor.

#What It Does

MoonModGuard parses moon.mod and moon.pkg text, extracts module metadata and package imports, builds an in-memory project model, evaluates supply-chain policy risks, and renders a deterministic Markdown audit report.

The first version is deliberately dependency-free and accepts explicit strings or snapshots instead of scanning the filesystem. This keeps the core portable and testable while leaving room for later CI and workspace integrations.

#Installation

moon add Noverberrain/moonmodguard

#Why This Exists

MoonBit projects rely on compact manifest files for package identity, dependencies, metadata, and publication readiness. A small auditor can help maintainers check whether a package is ready to publish, whether metadata is complete, and whether dependency declarations match a local policy.

MoonModGuard targets software analysis and engineering quality workflows:

  • package release readiness checks
  • contest repository review
  • classroom or team repository governance
  • dependency policy demonstration
  • future CI or package registry audit integration

#Features

  • Parse scalar fields from moon.mod: name, version, license, readme, repository, description.
  • Parse array fields such as keywords = [ "audit", "moonbit" ].
  • Parse import blocks from moon.mod and moon.pkg.
  • Build a project model from module and package manifests.
  • Evaluate policy diagnostics for missing metadata, disallowed licenses, unknown dependency prefixes, and duplicate dependencies.
  • Render a deterministic Markdown report.
  • Provide a runnable CLI demo.

#Quick Start

moon test moon run cmd/main

Example CLI output:

MoonModGuard demo project=wyc060514/moonmodguard dependencies=2 risks=0 --- markdown --- # MoonModGuard Audit Report

#API Example

///|
test "audit a project" {
let manifest = match @moonmodguard.parse_mod(
"name = \"wyc060514/tool\"\nlicense = \"Apache-2.0\"\nreadme = \"README.md\"\nrepository = \"https://example.test/repo\"",
) {
Ok(value) => value
Err(err) => fail(@moonmodguard.format_error(err))
}
let report = @moonmodguard.evaluate_policy(
@moonmodguard.project_from(manifest, []),
@moonmodguard.default_policy(),
)
assert_eq(report.risk_count, 0)
}

Public API:

  • parse_mod(input : String) -> Result[ModuleManifest, GuardError]
  • parse_pkg(input : String) -> Result[PackageManifest, GuardError]
  • project_from(manifest : ModuleManifest, packages : Array[PackageManifest]) -> ProjectModel
  • scan_project(snapshot : ProjectSnapshot) -> AuditReport
  • default_policy() -> Policy
  • evaluate_policy(project : ProjectModel, policy : Policy) -> AuditReport
  • render_markdown(report : AuditReport) -> String
  • format_error(err : GuardError) -> String

#Consumer Guide

#CLI Usage

# Audit a moon.mod file, output Markdown report moon run cmd/main -- moon.mod # Output JSON instead of Markdown moon run cmd/main -- moon.mod --json # Use EnhancedPolicy with trust grading moon run cmd/main -- moon.mod --enhanced

#EnhancedPolicy with Trust Grading

let manifest = match @moonmodguard.parse_mod(mod_text) {
Ok(m) => m
Err(_) => abort("parse error")
}
let project = @moonmodguard.project_from(manifest, [])
let report = @moonmodguard.evaluate_enhanced(project, @moonmodguard.EnhancedPolicy::default())
println(@moonmodguard.render_full_report(report))

EnhancedPolicy adds CSP-style trust grading (Trusted / Allowed / Blocked), max-dependency limits, and version validation on top of the base policy checks.

#Trust Policy Builder

let policy = @moonmodguard.TrustPolicy::new()
.trust("moonbitlang/")
.allow("github.com/")
.block("bad-domain/")
.default_level(@moonmodguard.Blocked)

let level = policy.check("moonbitlang/x")
// level is Trusted

#Batch Audit

let snapshots = [
@moonmodguard.ProjectSnapshot::{ module_manifest: m1, packages: [] },
@moonmodguard.ProjectSnapshot::{ module_manifest: m2, packages: [] },
]
let summary = @moonmodguard.batch_audit(snapshots)
println(@moonmodguard.render_batch_summary(summary))

#Versioned Dependencies

The parser now supports @version in dependency declarations:

import { "moonbitlang/x@0.4.46" @x }

Use check_version_consistency to validate declared versions, and check_missing_versioned_dep to match module-level versioned deps against package-level imports.

#JSON Output

let json = @moonmodguard.render_json(report)
// All string values are properly escaped (quotes, backslashes, newlines, tabs)

#SARIF Output (GitHub Code Scanning)

let sarif = @moonmodguard.render_sarif(report, "MoonModGuard")
// Valid SARIF v2.1.0 JSON for GitHub Code Scanning upload

#Workspace Scanner

# Recursively audit all packages under a directory moon run cmd/main -- --workspace .

let pkgs = @moonmodguard.discover_packages(".") raise
let summary = @moonmodguard.audit_workspace(".") raise
println(@moonmodguard.render_batch_summary(summary))

#GitHub Annotations

# Output as GitHub Actions workflow commands moon run cmd/main -- moon.mod --annotations

#mooncake.yaml Consistency

let mooncake = match @moonmodguard.parse_mooncake(yaml_text) {
Ok(m) => m
Err(_) => abort("parse error")
}
let diags = @moonmodguard.check_mooncake_consistency(mod_manifest, mooncake)
// Reports name/version/license/repository/description/keywords mismatches

#Full Audit & Dependency Analysis

full_audit runs the base policy plus every dependency check in one report:

let report = @moonmodguard.full_audit(manifest, packages)
// Combines evaluate_policy with:
// check_unused_dependency, check_missing_versioned_dep,
// check_version_consistency, check_version_conflicts,
// check_self_dependency

Version conflict and self-dependency detection:

// Same source declared with two versions -> "version-conflict"
let conflicts = @moonmodguard.check_version_conflicts(manifest, packages)

// Module imports its own name -> "self-dependency"
let self = @moonmodguard.check_self_dependency(manifest)

Repository URL validation:

@moonmodguard.is_valid_repository_url("https://github.com/a/b") // true
@moonmodguard.is_valid_repository_url("not-a-url") // false

Public API additions:

  • check_missing_versioned_dep(manifest, packages) -> Array[Diagnostic]
  • check_version_consistency(manifest) -> Array[Diagnostic]
  • check_version_conflicts(manifest, packages) -> Array[Diagnostic]
  • check_self_dependency(manifest) -> Array[Diagnostic]
  • full_audit(manifest, packages) -> AuditReport
  • is_valid_repository_url(repository) -> Bool
  • evaluate_enhanced(project, policy) -> AuditReport
  • render_json(report) -> String
  • render_full_report(report) -> String
  • render_summary(report) -> String
  • batch_audit(snapshots) -> AuditSummary
  • render_batch_summary(summary) -> String
  • TrustPolicy::new() / .trust() / .allow() / .block() / .default_level()
  • EnhancedPolicy::default()

Dependency struct now includes version : String? for versioned dependency declarations.

#Design Notes

The parser handles the common MoonBit manifest shape used by package metadata and import declarations. It is not a full MoonBit grammar parser. That boundary is intentional: the first release focuses on release readiness and policy audit checks that can be validated with stable tests.

The default policy accepts Apache-2.0, MIT, and MulanPSL-2.0, and treats moonbitlang/ and wyc060514/ as trusted dependency prefixes. Callers can pass a custom Policy value for stricter project rules.

#Competition Materials

  • Proposal source: docs/competition/proposal.md
  • Submission guide: docs/competition/submission-guide.md
  • Acceptance checklist: docs/competition/acceptance-checklist.md
  • Application PDF: docs/competition/MoonModGuard项目申报书.pdf

#License

Apache-2.0

#
AuditReport

pub(all) struct AuditReport {
project : ProjectModel
diagnostics : Array[Diagnostic]
risk_count : Int
dependency_count : Int
} derive(Eq)

#
AuditSummary

pub(all) struct AuditSummary {
total_projects : Int
total_diagnostics : Int
projects_clean : Int
projects_risky : Int
} derive(Eq)

#
Dependency

pub(all) struct Dependency {
source : String
version : String?
as_name : String?
} derive(Eq)

#
Diagnostic

pub(all) struct Diagnostic {
code : String
message : String
severity : String
} derive(Eq)

#
DiscoveredPackage

pub(all) struct DiscoveredPackage {
dir : String
mod_text : String
pkg_text : String
mooncake_text : String?
} derive(Eq)

A discovered MoonBit package in a workspace.

#
EnhancedPolicy

pub(all) struct EnhancedPolicy {
trust_policy : TrustPolicy
require_readme : Bool
require_repository : Bool
require_license : Bool
require_description : Bool
min_description_length : Int
require_keywords : Bool
min_keywords : Int
require_version : Bool
max_dependencies : Int
} derive(Eq)

#
EnhancedPolicy::default

#
GuardError

pub(all) enum GuardError {
UnterminatedString(Position)
} derive(Eq)

#
ModuleManifest

pub(all) struct ModuleManifest {
name : String
version : String
readme : String
repository : String
license : String
keywords : Array[String]
description : String
imports : Array[Dependency]
} derive(Eq)

#
MooncakeManifest

pub(all) struct MooncakeManifest {
name : String
version : String
description : String
repository : String
license : String
keywords : Array[String]
} derive(Eq)

Parsed fields from a mooncake.yaml file.

#
PackageManifest

pub(all) struct PackageManifest {
imports : Array[Dependency]
} derive(Eq)

#
Policy

pub(all) struct Policy {
allowed_licenses : Array[String]
trusted_prefixes : Array[String]
require_readme : Bool
require_repository : Bool
require_license : Bool
require_description : Bool
min_description_length : Int
require_keywords : Bool
min_keywords : Int
require_valid_version : Bool
require_valid_repository : Bool
} derive(Eq)

#
Position

pub(all) struct Position {
line : Int
column : Int
} derive(Eq)

#
PositionedDiagnostic

pub(all) struct PositionedDiagnostic {
code : String
message : String
severity : String
file : String
line : Int
} derive(Eq)

Location-aware diagnostic with file path and position.

#
ProjectModel

pub(all) struct ProjectModel {
name : String
version : String
license : String
readme : String
repository : String
description : String
keywords : Array[String]
dependencies : Array[Dependency]
} derive(Eq)

#
ProjectSnapshot

pub(all) struct ProjectSnapshot {
module_manifest : ModuleManifest
packages : Array[PackageManifest]
} derive(Eq)

#
TrustLevel

pub(all) enum TrustLevel {
Trusted
Allowed
Blocked
} derive(Eq)

#
TrustPolicy

pub(all) struct TrustPolicy {
rules : Array[TrustRule]
default_level : TrustLevel
} derive(Eq)

#
TrustPolicy::allow

fn TrustPolicy::allow(self : TrustPolicy, prefix : String) -> TrustPolicy

#
TrustPolicy::block

fn TrustPolicy::block(self : TrustPolicy, prefix : String) -> TrustPolicy

#
TrustPolicy::check

fn TrustPolicy::check(self : TrustPolicy, source : String) -> TrustLevel

#
TrustPolicy::default_level

fn TrustPolicy::default_level(self : TrustPolicy, level : TrustLevel) -> TrustPolicy

#
TrustPolicy::new

#
TrustPolicy::trust

fn TrustPolicy::trust(self : TrustPolicy, prefix : String) -> TrustPolicy

#
TrustRule

pub(all) struct TrustRule {
prefix : String
level : TrustLevel
} derive(Eq)

#
audit_policy

fn audit_policy(project : ProjectModel, policy : Policy) -> AuditReport

Run an enhanced policy evaluation that includes version validation.

#
audit_snapshot

fn audit_snapshot(mod_text : String, pkg_texts : Array[String]) -> Result[AuditReport, GuardError]

Convenience: parse mod text and package texts, then run the default policy audit.

#
audit_workspace

fn audit_workspace(root : String) -> AuditSummary raise

Batch-audit all packages discovered under a root directory.

#
batch_audit

fn batch_audit(snapshots : Array[ProjectSnapshot]) -> AuditSummary

Run a batch audit on multiple projects and return a summary.

#
check_missing_versioned_dep

fn check_missing_versioned_dep(manifest : ModuleManifest, packages : Array[PackageManifest]) -> Array[Diagnostic]

Check that every package-level import has a corresponding versioned module dependency.

#
check_mooncake_consistency

fn check_mooncake_consistency(mod_manifest : ModuleManifest, mooncake : MooncakeManifest) -> Array[Diagnostic]

Check consistency between moon.mod and mooncake.yaml manifests.

#
check_self_dependency

fn check_self_dependency(manifest : ModuleManifest) -> Array[Diagnostic]

Check for self-dependency: the module declares a dependency on its own name.

#
check_unused_dependency

fn check_unused_dependency(manifest : ModuleManifest, packages : Array[PackageManifest]) -> Array[Diagnostic]

Check if a dependency declared in the module manifest is actually imported by any package.

#
check_version_conflicts

fn check_version_conflicts(manifest : ModuleManifest, packages : Array[PackageManifest]) -> Array[Diagnostic]

Check for version conflicts: the same dependency source declared with two or more different versions across the module and its packages.

#
check_version_consistency

fn check_version_consistency(manifest : ModuleManifest) -> Array[Diagnostic]

Check that versioned module dependencies match the versions actually imported.

#
count_by_severity

fn count_by_severity(diagnostics : Array[Diagnostic], severity : String) -> Int

Return the diagnostic count for a severity level.

#
default_policy

fn default_policy() -> Policy

#
discover_packages

fn discover_packages(root : String) -> Array[DiscoveredPackage] raise

Recursively discover MoonBit packages under a root directory.

Returns an array of DiscoveredPackage, one per directory that contains a moon.mod or moon.pkg file. Directories named _build, .mooncakes, .git, and target are skipped.

#
evaluate_enhanced

fn evaluate_enhanced(project : ProjectModel, policy : EnhancedPolicy) -> AuditReport

#
evaluate_policy

fn evaluate_policy(project : ProjectModel, policy : Policy) -> AuditReport

#
format_error

fn format_error(err : GuardError) -> String

#
full_audit

fn full_audit(manifest : ModuleManifest, packages : Array[PackageManifest]) -> AuditReport

Run a full audit: base policy plus every dependency and metadata check.

Combines evaluate_policy with the standalone dependency checks (check_unused_dependency, check_missing_versioned_dep, check_version_consistency, check_version_conflicts, check_self_dependency) into a single report.

#
is_risky_license

fn is_risky_license(license : String) -> Bool

Check for known-risky licenses (GPL, AGPL, etc.).

#
is_valid_repository_url

fn is_valid_repository_url(repository : String) -> Bool

Check that a repository field is a plausible version-control URL. Accepts https://, http://, git://, ssh:// and git@host:path forms.

#
keyword_quality

fn keyword_quality(keywords : Array[String], min : Int) -> Bool

Check keyword quality: at least min meaningful keywords (length >= 3).

#
parse_mod

fn parse_mod(input : String) -> Result[ModuleManifest, GuardError]

#
parse_mooncake

fn parse_mooncake(input : String) -> Result[MooncakeManifest, GuardError]

Parse a mooncake.yaml text into a MooncakeManifest.

#
parse_pkg

fn parse_pkg(input : String) -> Result[PackageManifest, GuardError]

#
permissive_policy

fn permissive_policy() -> Policy

#
positioned_diagnostic

fn positioned_diagnostic(code : String, message : String, file : String, line : Int) -> PositionedDiagnostic

Convert a Position to a diagnostic with location info.

#
project_from

fn project_from(manifest : ModuleManifest, packages : Array[PackageManifest]) -> ProjectModel

#
render_annotation

fn render_annotation(d : PositionedDiagnostic) -> String

Render a positioned diagnostic as a GitHub Actions annotation.

#
render_batch_summary

fn render_batch_summary(summary : AuditSummary) -> String

Render a batch audit result as a concise table.

#
render_full_report

fn render_full_report(report : AuditReport) -> String

Render a full audit report with a summary section.

#
render_json

fn render_json(report : AuditReport) -> String

#
render_markdown

fn render_markdown(report : AuditReport) -> String

#
render_sarif

fn render_sarif(report : AuditReport, tool_name : String) -> String

Render an audit report as SARIF v2.1.0 JSON for GitHub Code Scanning.

#
render_summary

fn render_summary(report : AuditReport) -> String

Render a summary table of diagnostics grouped by severity.

#
report_from

fn report_from(name : String, license : String, readme : String, repository : String, description : String, keywords : Array[String], deps : Array[Dependency]) -> AuditReport

Build a report from explicit fields (no parsing needed).

#
scan_project

fn scan_project(snapshot : ProjectSnapshot) -> AuditReport

#
strict_policy

fn strict_policy() -> Policy

#
validate_name

fn validate_name(name : String) -> Bool

Check that the project name follows the owner/package convention.

#
validate_version

fn validate_version(version : String) -> Bool

Validate a version string against full semver rules.

Supports: MAJOR.MINOR.PATCH[-prerelease][+build]

Source Files