Data-contract evolution analyzer with minimal counterexample generation
contract Checkout 1.0.0
type Order closed
field id string required minlen=1 maxlen=64
field total int required min=0 max=100000
field status enum:pending|paid|failed required
field note string optional maxlen=200
endcontract Checkout 2.0.0
type Order closed
field id string required minlen=8 maxlen=64
field total int required min=1 max=100000
field status enum:pending|paid required
field note string optional maxlen=200
field currency string required minlen=3 maxlen=3
end{"id":"a","total":0,"status":"failed"}moon add CJR-ai-nb/evowitness@0.1.0import {
"CJR-ai-nb/evowitness" @evowitness,
}let old_source =
#|contract C 1
#|type Item closed
#|field id string required
#|end
let new_source =
#|contract C 2
#|type Item closed
#|field id string required
#|field region string required
#|end
match @evowitness.analyze_text(
old_source,
new_source,
mode=@evowitness.Backward,
) {
Ok(report) => println(report.to_markdown())
Err(error) => println(error.render())
}moon check --deny-warn
moon build
moon test --deny-warn
moon run examples/checkout
moon run cmd/evowitness -- demo json
moon run cmd/evowitness -- demo witness-jsonl
moon run cmd/evowitness -- demo score-json
moon run cmd/evowitness -- demo replay-checklistmoon run cmd/evowitness -- compare backward \
"contract C 1;type Item closed;field id string required;end" \
"contract C 2;type Item closed;field id string required;field zone string required;end" \
markdown| 模式 | 判断的问题 | 常见部署场景 |
|---|---|---|
| backward | 新契约能否读取全部旧数据 | 先升级消费者 |
| forward | 旧契约能否读取全部新数据 | 先升级生产者或存在旧客户端 |
| full | 两个方向是否都安全 | 滚动发布、离线同步、事件总线 |
moon check --deny-warn
moon build
moon test --deny-warn
moon run examples/checkout
moon run cmd/evowitness -- demo json
moon run cmd/evowitness -- demo witness-jsonl
moon publish --dry-runmoon login
moon publish --dry-run
moon publishpub(all) struct AnalysisPolicy {
name : String
required_mode : CompatibilityMode?
max_unallowed_breaking : Int
max_warnings : Int
forbidden_codes : Array[String]
require_witness : Bool
allowances : Array[ChangeAllowance]
} derive(Eq, Debug)fn AnalysisPolicy::allow(self : AnalysisPolicy, code : String, path_prefix? : String, direction? : String, reason~ : String) -> AnalysisPolicypub(all) struct AnalysisReport {
contract_name : String
old_version : String
new_version : String
mode : CompatibilityMode
changes : Array[Change]
} derive(Eq, Debug)pub(all) struct Contract {
name : String
version : String
objects : Array[ObjectType]
} derive(Eq, Debug)pub(all) struct EvidenceScoreIssue {
code : String
level : EvidenceScoreLevel
case_id : String?
path : String
message : String
suggestion : String
} derive(Eq, Debug)pub(all) struct EvidenceScorecard {
contract_name : String
old_version : String
new_version : String
mode : CompatibilityMode
breaking_count : Int
covered_breaking_count : Int
case_count : Int
duplicate_case_ids : Int
duplicate_payloads : Int
sensitive_case_count : Int
weak_reason_count : Int
issues : Array[EvidenceScoreIssue]
} derive(Eq, Debug)pub(all) struct Field {
name : String
type_expr : TypeExpr
required : Bool
constraints : Constraints
line : Int
} derive(Eq, Debug)pub(all) struct MigrationPlan {
contract_name : String
from_version : String
to_version : String
risk : String
steps : Array[MigrationStep]
} derive(Eq, Debug)pub(all) struct MigrationStep {
id : String
phase : MigrationPhase
path : String
action : String
verification : String
related_codes : Array[String]
} derive(Eq, Debug)pub(all) struct ReplayStep {
id : String
case_id : String
kind : ReplayStepKind
contract_version : String
expected_valid : Bool
path : String
payload : String
assertion : String
note : String
} derive(Eq, Debug)pub(all) struct ReplaySuite {
contract_name : String
old_version : String
new_version : String
mode : CompatibilityMode
case_count : Int
steps : Array[ReplayStep]
} derive(Eq, Debug)pub(all) struct ValidationResult {
object_name : String
issues : Array[ValidationIssue]
} derive(Eq, Debug)pub(all) struct WitnessPack {
contract_name : String
old_version : String
new_version : String
mode : CompatibilityMode
cases : Array[WitnessCase]
} derive(Eq, Debug)pub(all) struct WitnessVerification {
source_valid : Bool
target_valid : Bool
source_issues : Array[ValidationIssue]
target_issues : Array[ValidationIssue]
} derive(Eq, Debug)fn analyze(old_contract : Contract, new_contract : Contract, mode? : CompatibilityMode) -> AnalysisReportfn analyze_support_window(contracts : Array[Contract], mode? : CompatibilityMode) -> Array[AnalysisReport]fn analyze_text(old_source : String, new_source : String, mode? : CompatibilityMode) -> Result[AnalysisReport, ParseError]fn field_path(object_name : String, field_name : String) -> Stringcontract <name> <version>
type <name> <open|closed>
field <name> <kind> <required|optional> [constraint=value ...]
endfn validate_json(contract : Contract, object_name : String, input : String) -> Result[ValidationResult, InstanceError]fn validate_value(contract : Contract, object_name : String, value : Json) -> Result[ValidationResult, InstanceError]fn verify_witness(source : Contract, target : Contract, object_name : String, witness : Witness) -> Result[WitnessVerification, InstanceError]Data-contract evolution analyzer with minimal counterexample generation