保留交付执行序列化错误

补充交付账本和执行状态的 serde 原因

通过 Direct 执行回归测试
This commit is contained in:
kdletters
2026-10-05 19:18:06 +08:00
parent 88507e08b0
commit 3ecff3f524
2 changed files with 33 additions and 22 deletions
@@ -301,13 +301,13 @@ struct InitialDelivery {
fn initial_delivery_path(root: &Path) -> Result<PathBuf, String> {
let root = root
.canonicalize()
.map_err(|_| "delivery-project-unavailable")?;
.map_err(|error| format!("delivery-project-unavailable:{error}"))?;
let host = crate::game_creator_runtime_config_dir()
.ok_or("delivery-host-unavailable: 缺少客户端私有目录")?;
crate::ensure_game_creator_private_directory_tree(&host, "客户端交付目录")?;
let host = host
.canonicalize()
.map_err(|_| "delivery-host-unavailable")?;
.map_err(|error| format!("delivery-host-unavailable:{error}"))?;
if host.starts_with(&root) {
return Err("delivery-host-invalid: 权威交付状态不能位于项目内".into());
}
@@ -322,14 +322,14 @@ fn initial_delivery_path(root: &Path) -> Result<PathBuf, String> {
fn read_initial_delivery(path: &Path, project_id: &str) -> Result<Option<InitialDelivery>, String> {
if !path
.try_exists()
.map_err(|_| "delivery-initial-state-unavailable")?
.map_err(|error| format!("delivery-initial-state-unavailable:{error}"))?
{
return Ok(None);
}
let value: InitialDelivery = serde_json::from_str(
&crate::read_game_creator_private_file_to_string(path, "首次交付状态", 16 * 1024)?,
)
.map_err(|_| "delivery-initial-state-invalid")?;
.map_err(|error| format!("delivery-initial-state-invalid:{error}"))?;
if value.schema_version != "agc-initial-web-delivery.v1" || value.project_id != project_id {
return Err("delivery-initial-state-identity".into());
}
@@ -358,7 +358,7 @@ fn initial_requirement_at(
let receipt = resolve_local_project_path(root, ".agent/runtime/web-scaffold-preparation.json")?;
if !receipt
.try_exists()
.map_err(|_| "delivery-scaffold-receipt-unavailable")?
.map_err(|error| format!("delivery-scaffold-receipt-unavailable:{error}"))?
{
return Ok(false);
}
@@ -366,12 +366,12 @@ fn initial_requirement_at(
let mut bytes = Vec::new();
file.take(16 * 1024 + 1)
.read_to_end(&mut bytes)
.map_err(|_| "delivery-scaffold-receipt-unavailable")?;
.map_err(|error| format!("delivery-scaffold-receipt-unavailable:{error}"))?;
if bytes.len() > 16 * 1024 {
return Err("delivery-scaffold-receipt-invalid".into());
}
let receipt: Value =
serde_json::from_slice(&bytes).map_err(|_| "delivery-scaffold-receipt-invalid")?;
let receipt: Value = serde_json::from_slice(&bytes)
.map_err(|error| format!("delivery-scaffold-receipt-invalid:{error}"))?;
let expected: BTreeMap<_, _> = crate::project::trusted_web_scaffold_files()
.into_iter()
.map(|(path, content)| (path, format!("{:x}", Sha256::digest(content.as_bytes()))))
@@ -379,7 +379,8 @@ fn initial_requirement_at(
if receipt["schemaVersion"] != "agc-web-scaffold-preparation.v1"
|| receipt["projectId"] != project.project_id
|| receipt["templateHashes"]
!= serde_json::to_value(expected).map_err(|_| "delivery-scaffold-template-invalid")?
!= serde_json::to_value(expected)
.map_err(|error| format!("delivery-scaffold-template-invalid:{error}"))?
{
return Err("delivery-scaffold-receipt-invalid".into());
}
@@ -390,7 +391,7 @@ fn initial_requirement_at(
project_id: project.project_id,
completed: false,
})
.map_err(|_| "delivery-initial-state-invalid")?,
.map_err(|error| format!("delivery-initial-state-invalid:{error}"))?,
"首次交付状态",
)?;
Ok(true)
@@ -423,7 +424,7 @@ fn mark_initial_delivered(root: &Path, ledger: &ExecutionLedger) -> Result<(), S
project_id: ledger.project_id.clone(),
completed: true,
})
.map_err(|_| "delivery-initial-state-invalid")?,
.map_err(|error| format!("delivery-initial-state-invalid:{error}"))?,
"首次交付状态",
)
}
@@ -438,18 +439,18 @@ pub(super) async fn register_contract(
let input = input.clone();
tokio::task::spawn_blocking(move || {
let state = session.snapshot()?;
if root.canonicalize().map_err(|_| "delivery-project-unavailable")? != session.root { return Err("delivery-project-identity".into()); }
if root.canonicalize().map_err(|error| format!("delivery-project-unavailable:{error}"))? != session.root { return Err("delivery-project-identity".into()); }
let new_web = state.contract.as_ref().and_then(|value| value["newWebGame"].as_bool()).unwrap_or(state.requires_contract);
let mut contract = normalized_contract(&input, new_web)?;
contract.initial_artifact_hashes = if let Some(existing) = &state.contract {
serde_json::from_value::<FrozenContract>(existing.clone()).map_err(|_| "delivery-frozen-contract-invalid")?.initial_artifact_hashes
serde_json::from_value::<FrozenContract>(existing.clone()).map_err(|error| format!("delivery-frozen-contract-invalid:{error}"))?.initial_artifact_hashes
} else {
contract.requirements.iter().filter_map(|criterion| match criterion {
Criterion::Artifact {id,path} => Some((id.clone(),artifact_hash(&root,path).ok())),
_ => None,
}).collect()
};
let value = serde_json::to_value(contract).map_err(|_| "delivery-contract-invalid")?;
let value = serde_json::to_value(contract).map_err(|error| format!("delivery-contract-invalid:{error}"))?;
let frozen = session.freeze_contract(value)?;
Ok(json!({"status":"frozen","contract":frozen,"next":"只推进已登记范围;使用托管构建/验证和固定场景提供真实证据"}))
}).await.map_err(|error| format!("delivery-contract-worker-exited:{error}"))?
@@ -534,8 +535,8 @@ fn assess(root: &Path, ledger: &ExecutionLedger) -> Result<Assessment, String> {
checks: vec![json!({"id":"contract","passed":false,"detail":"尚未登记本轮验收合同"})],
});
};
let contract: FrozenContract =
serde_json::from_value(value.clone()).map_err(|_| "delivery-frozen-contract-invalid")?;
let contract: FrozenContract = serde_json::from_value(value.clone())
.map_err(|error| format!("delivery-frozen-contract-invalid:{error}"))?;
if contract.schema_version != CONTRACT_SCHEMA {
return Err("delivery-frozen-contract-invalid".into());
}
@@ -688,7 +689,10 @@ pub(super) async fn finish_sealing(
if !first.ready || !final_check.ready || first.runtime_fingerprint != final_check.runtime_fingerprint || first.checks != final_check.checks {
session.reopen_for_repair()?; return Ok(None);
}
let contract: FrozenContract = serde_json::from_value(ledger.contract.clone().ok_or("delivery-contract-missing")?).map_err(|_| "delivery-contract-invalid")?;
let contract: FrozenContract = serde_json::from_value(
ledger.contract.clone().ok_or("delivery-contract-missing")?,
)
.map_err(|error| format!("delivery-contract-invalid:{error}"))?;
let report = format!("本轮已完成宿主验收。\n\n范围:{}\n\n{}\n\n执行/返修批次:{}/{}。仅证明以上登记范围;固定场景不代表完整长关卡已通关。执行预算按宿主观察的占用计入;第三方并发调用可能按组占用上界计入。", truncate_agent_runtime_text(&contract.scope,1200),
final_check.checks.iter().map(|check|format!("- 已通过:{}",check["label"].as_str().unwrap_or("验收项"))).collect::<Vec<_>>().join("\n"), ledger.used_passes,ledger.max_runs);
session.complete(report.clone())?;
@@ -559,11 +559,15 @@ pub(super) fn open_with_analytics_at(
"宿主执行状态",
MAX_STATE_BYTES as u64,
)
.map_err(|_| {
"direct-execution-private-state: 无法安全读取宿主状态,禁止重置预算"
.map_err(|error| {
format!(
"direct-execution-private-state: 无法安全读取宿主状态,禁止重置预算:{error}"
)
})?,
)
.map_err(|_| "direct-execution-corrupt: 宿主执行状态无效,禁止重置预算")?,
.map_err(|error| {
format!("direct-execution-corrupt: 宿主执行状态无效,禁止重置预算:{error}")
})?,
)
} else {
None
@@ -758,7 +762,7 @@ impl ExecutionSession {
}
next.revision = data.ledger.revision.saturating_add(1);
let bytes = serde_json::to_vec(&next)
.map_err(|_| "direct-execution-persistence: 状态序列化失败")?;
.map_err(|error| format!("direct-execution-persistence: 状态序列化失败:{error}"))?;
if bytes.len() > MAX_STATE_BYTES {
return Err("direct-execution-capacity: 宿主状态超过保留上限".into());
}
@@ -829,7 +833,10 @@ impl ExecutionSession {
pub(super) fn update_plan(&self, plan: Value) -> Result<Value, String> {
self.tick()?;
if !plan.is_object()
|| serde_json::to_vec(&plan).map_err(|_| "计划格式无效")?.len() > 32 * 1024
|| serde_json::to_vec(&plan)
.map_err(|error| format!("计划格式无效:{error}"))?
.len()
> 32 * 1024
{
return Err("direct-plan-invalid: 计划必须为有界对象".into());
}