///|
fn usage() -> Unit {
println("MoonPolicyDiff: offline policy change audit")
println(
"Usage: moon run cmd/main -- audit BEFORE AFTER UNIVERSE [text|json|attribution|groups|witnesses|structural]",
)
println(" moon run cmd/main -- coverage POLICY UNIVERSE")
println(" moon run cmd/main -- lint POLICY UNIVERSE")
println(" moon run cmd/main -- impact POLICY UNIVERSE")
println(" moon run cmd/main -- scope UNIVERSE ACTION1,ACTION2")
println(" moon run cmd/main -- verify POLICY UNIVERSE EXPECTATIONS")
println(
" moon run cmd/main -- release-audit BEFORE.json AFTER.json SEEDS.json [PLAN.json]",
)
println(
" moon run cmd/main -- release-cases BEFORE.json AFTER.json SEEDS.json [PLAN.json]",
)
println(" moon run cmd/main -- release-replay POLICY.json CASES.json")
println("Exit: 0 pass, 1 gate failure, 2 invalid input")
}
///|
fn release_command(args : Array[String]) -> Unit {
try {
match (load(args[3]), load(args[4]), load(args[5])) {
(Some(before), Some(after), Some(seeds)) => {
let plan = if args.length() == 7 {
match load(args[6]) {
Some(text) => @lib.parse_release_plan(text)
None => {
@sys.exit(2)
return
}
}
} else {
@lib.default_release_plan()
}
let run = @lib.run_moonpolicy_json(before, after, seeds, plan)
if args[2] == "release-cases" {
// Never export a partial corpus as a valid regression artifact.
if !run.report.complete {
println(@lib.release_run_json(run).stringify())
@sys.exit(2)
return
}
println(@lib.release_cases_json(run.report).stringify())
} else {
println(@lib.release_run_json(run).stringify())
}
match run.report.verdict {
@lib.Pass => ()
@lib.Fail => @sys.exit(1)
@lib.Indeterminate => @sys.exit(2)
}
}
_ => @sys.exit(2)
}
} catch {
@lib.InvalidReleaseInput(message) => {
println("Invalid release input: \{message}")
@sys.exit(2)
}
}
}
///|
fn load(path : String) -> String? {
Some(@fs.read_file_to_string(path)) catch {
_ => {
println("Cannot read \{path}")
None
}
}
}
///|
fn inspect(policy_path : String, universe_path : String, mode : String) -> Unit {
match (load(policy_path), load(universe_path)) {
(Some(policy_text), Some(universe_text)) => {
let policy = @lib.parse_policy(policy_text)
let universe = @lib.parse_universe(universe_text)
if @lib.has_errors(policy.diagnostics) ||
@lib.has_errors(universe.diagnostics) {
for issue in policy.diagnostics {
println("\{issue.code}: \{issue.message}")
}
for issue in universe.diagnostics {
println("\{issue.code}: \{issue.message}")
}
@sys.exit(2)
return
}
match mode {
"lint" =>
println(
@lib.render_lint(@lib.lint_policy(policy.policy, universe.universe)),
)
"coverage" =>
println(
@lib.render_coverage(
@lib.measure_policy_coverage(policy.policy, universe.universe),
),
)
"impact" => {
let report = @lib.measure_rule_impact(
policy.policy,
universe.universe,
)
println(@lib.render_rule_impact(report))
if !report.complete {
@sys.exit(2)
}
}
_ => @sys.exit(2)
}
}
_ => @sys.exit(2)
}
}
///|
fn main {
let args = @env.args()
if (args.length() == 6 || args.length() == 7) &&
(args[2] == "release-audit" || args[2] == "release-cases") {
release_command(args)
return
}
if args.length() == 5 && args[2] == "release-replay" {
try {
match (load(args[3]), load(args[4])) {
(Some(policy), Some(cases)) => {
let result = @lib.replay_moonpolicy_json(policy, cases)
println(result.stringify())
match result {
{ "failed": Number(failed, ..), .. } =>
if failed > 0 {
@sys.exit(1)
}
_ => @sys.exit(2)
}
}
_ => @sys.exit(2)
}
} catch {
@lib.InvalidReleaseInput(message) => {
println("Invalid replay input: \{message}")
@sys.exit(2)
}
}
return
}
if args.length() == 5 && args[2] == "coverage" {
inspect(args[3], args[4], "coverage")
return
}
if args.length() == 5 && args[2] == "lint" {
inspect(args[3], args[4], "lint")
return
}
if args.length() == 5 && args[2] == "impact" {
inspect(args[3], args[4], "impact")
return
}
if args.length() == 5 && args[2] == "scope" {
match load(args[3]) {
Some(universe_text) => {
let universe = @lib.parse_universe(universe_text)
if @lib.has_errors(universe.diagnostics) {
println("Invalid universe")
@sys.exit(2)
return
}
let actions : Array[String] = []
for item in args[4].split(",") {
actions.push(item.to_owned())
}
let scope = @lib.analyze_scope(universe.universe, actions, 100000)
println(@lib.render_scope(scope))
if !scope.complete {
@sys.exit(2)
}
}
_ => @sys.exit(2)
}
return
}
if args.length() == 6 && args[2] == "verify" {
match (load(args[3]), load(args[4]), load(args[5])) {
(Some(policy_text), Some(universe_text), Some(expectation_text)) => {
let policy = @lib.parse_policy(policy_text)
let universe = @lib.parse_universe(universe_text)
let expectations = @lib.parse_expectations(expectation_text)
if @lib.has_errors(policy.diagnostics) ||
@lib.has_errors(universe.diagnostics) ||
@lib.has_errors(expectations.diagnostics) {
println("Invalid verification input")
@sys.exit(2)
return
}
let result = @lib.verify_expectations(
policy.policy,
universe.universe,
expectations.expectations,
)
println(@lib.render_verification(result))
if result.inconclusive > 0 {
@sys.exit(2)
} else if result.failed > 0 {
@sys.exit(1)
}
}
_ => @sys.exit(2)
}
return
}
if (args.length() != 6 && args.length() != 7) || args[2] != "audit" {
usage()
@sys.exit(2)
return
}
match (load(args[3]), load(args[4]), load(args[5])) {
(Some(before_text), Some(after_text), Some(universe_text)) => {
let before = @lib.parse_policy(before_text)
let after = @lib.parse_policy(after_text)
let universe = @lib.parse_universe(universe_text)
if @lib.has_errors(before.diagnostics) ||
@lib.has_errors(after.diagnostics) ||
@lib.has_errors(universe.diagnostics) {
for item in before.diagnostics {
println("\{item.code}: \{item.message}")
}
for item in after.diagnostics {
println("\{item.code}: \{item.message}")
}
for item in universe.diagnostics {
println("\{item.code}: \{item.message}")
}
@sys.exit(2)
return
}
let report = @lib.compare_policies(
before.policy,
after.policy,
universe.universe,
)
let gate = @lib.check_gate(report, universe.universe, @lib.strict_gate())
let format = if args.length() == 7 { args[6] } else { "text" }
match format {
"text" => println(@lib.render_report(report, gate))
"json" => println(@lib.render_report_json(report, gate))
"groups" =>
println(
@lib.render_groups(@lib.group_changes(report, universe.universe)),
)
"witnesses" =>
println(
@lib.render_witnesses(
@lib.select_witnesses(report, universe.universe, 100),
),
)
"structural" =>
println(
@lib.render_structural_diff(
@lib.structural_diff(before.policy, after.policy),
),
)
"attribution" =>
println(
@lib.render_attribution(
@lib.attribute_changes(
before.policy,
after.policy,
universe.universe,
),
),
)
_ => {
usage()
@sys.exit(2)
return
}
}
match gate.verdict {
@lib.Pass => ()
@lib.Fail => @sys.exit(1)
@lib.Indeterminate => @sys.exit(2)
}
}
_ => @sys.exit(2)
}
}