/// engine_contract.mbt —— 迁移契约检查(R109 · Design by Contract 蒸馏)。
/// 任何状态迁移动作先过 Meyer 三件套只读预检(precondition / invariant / postcondition),
/// 任一失败整笔拒绝、状态 A 回稳(不落库)。纯计算只读不写库(决策建议,执行权在调用方/指挥官)。
///|
/// 动作 → 文档化目标态(postcondition 基准;TaskStatus::to_string 为中文态名)。
fn contract_target_status(action : String) -> String? {
match action {
"claim" => Some("已领取")
"split" => Some("拆分中")
"execute" => Some("执行中")
"submit" => Some("待验收")
"reject" => Some("已打回")
"retry" => Some("执行中")
"pause" => Some("已暂停")
"resume" => Some("已领取")
"reopen" => Some("已领取")
"complete" => Some("已完成")
"archive" => Some("已归档")
"mark_decomposing" => Some("拆分中")
_ => None
}
}
///|
/// 对任务值副本尝试执行动作(precondition 判定:Err 即前置失败,返回失败原文)。
fn contract_try(
task : @core.Task,
action : String,
assignee : String,
completed_by : String,
) -> Result[@core.Task, String] {
match action {
"claim" => task.claim(assignee~)
"split" => task.split()
"execute" => task.execute()
"submit" => task.submit()
"reject" => task.reject()
"retry" => task.retry()
"pause" => task.pause()
"resume" => task.resume_task()
"reopen" => task.reopen()
"complete" => task.complete(completed_by~)
"archive" => task.archive()
"mark_decomposing" => task.mark_decomposing()
_ => Err("未知动作: " + action)
}
}
///|
/// 领域不变量(throughout):迁移后状态上校验,任一违反返回原因。
/// IN-1 id 非空 / IN-2 depth≥1(K 值合法)/ IN-3 split_n≥1 /
/// IN-4 身份保持(id/project_dir/ns 迁移前后一致)/ IN-5 assignee 纪律(claim/reopen 除外)。
fn invariant_violations(
before : @core.Task,
after : @core.Task,
action : String,
) -> Array[String] {
let out : Array[String] = []
if after.id == "" {
out.push("IN-1 违反: 任务 id 为空")
}
if after.depth < 1 {
out.push(
"IN-2 违反: K 值非法(depth=" +
after.depth.to_string() +
",须 ≥1)",
)
}
if after.split_n < 1 {
out.push(
"IN-3 违反: split_n=" + after.split_n.to_string() + ",须 ≥1",
)
}
if after.id != before.id ||
after.project_dir != before.project_dir ||
after.ns != before.ns {
out.push(
"IN-4 违反: 身份字段在迁移后漂移(id/project_dir/ns 须保持)",
)
}
if action != "claim" &&
action != "reopen" &&
after.assignee != before.assignee {
out.push("IN-5 违反: assignee 在非认领/重开动作下漂移")
}
out
}
///|
/// 迁移契约检查(R109 · Design by Contract 蒸馏):precondition / invariant / postcondition 三件套只读预检。
/// 任一失败 → verdict=rejected 整笔拒绝,状态 A 回稳(不落库);全部通过 → verdict=allowed(建议仍走正式生命周期工具执行)。
pub fn FistEngine::transition_contract(
self : FistEngine,
task_id : String,
action : String,
assignee? : String = "agent",
completed_by? : String = "human_steward",
) -> Json {
match self.get_task(task_id) {
None =>
Json::object({
"ok": Json::boolean(false),
"task_id": Json::string(task_id),
"action": Json::string(action),
"note": Json::string("任务不存在: " + task_id),
})
Some(t) =>
match contract_try(t, action, assignee, completed_by) {
Err(msg) =>
Json::object({
"ok": Json::boolean(true),
"task_id": Json::string(task_id),
"action": Json::string(action),
"phase": Json::string("precondition"),
"verdict": Json::string("rejected"),
"reasons": Json::array([Json::string(msg)]),
"target_state": Json::string(""),
"state_stable": Json::boolean(true),
"note": Json::string(
"迁移契约检查(Design by Contract):precondition 失败 → 整笔拒绝,状态 A 回稳(不落库)。",
),
})
Ok(nt) =>
if invariant_violations(t, nt, action).length() > 0 {
Json::object({
"ok": Json::boolean(true),
"task_id": Json::string(task_id),
"action": Json::string(action),
"phase": Json::string("invariant"),
"verdict": Json::string("rejected"),
"reasons": Json::array(
invariant_violations(t, nt, action).map(Json::string),
),
"target_state": Json::string(""),
"state_stable": Json::boolean(true),
"note": Json::string(
"迁移契约检查(Design by Contract):precondition 通过但 invariant 违反 → 整笔拒绝,状态 A 回稳(不落库)。",
),
})
} else {
match contract_target_status(action) {
None =>
Json::object({
"ok": Json::boolean(true),
"task_id": Json::string(task_id),
"action": Json::string(action),
"phase": Json::string("postcondition"),
"verdict": Json::string("rejected"),
"reasons": Json::array([Json::string("动作无契约定义")]),
"target_state": Json::string(""),
"state_stable": Json::boolean(true),
"note": Json::string(
"迁移契约检查:动作不在契约表内。",
),
})
Some(target) =>
if nt.status_to_string() != target {
Json::object({
"ok": Json::boolean(true),
"task_id": Json::string(task_id),
"action": Json::string(action),
"phase": Json::string("postcondition"),
"verdict": Json::string("rejected"),
"reasons": Json::array([
Json::string(
"postcondition 违反: 目标态 [" +
nt.status_to_string() +
"] ≠ 文档化目标态 [" +
target +
"]",
),
]),
"target_state": Json::string(""),
"state_stable": Json::boolean(true),
"note": Json::string(
"迁移契约检查(Design by Contract):postcondition 失败 → 整笔拒绝,状态 A 回稳(不落库)。",
),
})
} else {
Json::object({
"ok": Json::boolean(true),
"task_id": Json::string(task_id),
"action": Json::string(action),
"phase": Json::string("all_pass"),
"verdict": Json::string("allowed"),
"reasons": Json::array([]),
"target_state": Json::string(target),
"state_stable": Json::boolean(true),
"note": Json::string(
"迁移契约检查(Design by Contract):precondition/invariant/postcondition 全通过,可安全迁移(建议仍走正式生命周期工具执行)。",
),
})
}
}
}
}
}
}