|
| 1 | +//! 占位文件任务诊断指引绑定首次报告、收据与当前输入;专用修复权限仍待验证闭环。 |
| 2 | +use super::Candidate; |
| 3 | +use crate::work_sync::c_family_placeholder_report::{ |
| 4 | + checker, current, fingerprint, positions, valid_shape, RULE, |
| 5 | +}; |
| 6 | +use serde_json::{Value, json}; |
| 7 | +use sha2::{Digest, Sha256}; |
| 8 | +use std::{fs, path::Path}; |
| 9 | + |
| 10 | +fn read(root: &Path, run: &str, digest: Option<&str>) -> Result<Value, &'static str> { |
| 11 | + if !super::safe_run_id(run) { |
| 12 | + return Err("clang_placeholder_origin_invalid"); |
| 13 | + } |
| 14 | + let bytes = super::read_bounded( |
| 15 | + &root.join(format!(".codeguard/reports/{run}.json")), |
| 16 | + 16 * 1024 * 1024, |
| 17 | + )?; |
| 18 | + let hash = format!("{:x}", Sha256::digest(&bytes)); |
| 19 | + if digest.is_some_and(|s| s != hash) { |
| 20 | + return Err("clang_placeholder_origin_changed"); |
| 21 | + } |
| 22 | + let r = codeguard_adapters::parse_unique_json(&bytes) |
| 23 | + .map_err(|_| "clang_placeholder_origin_invalid")?; |
| 24 | + if !valid_shape(&r) || r["run_id"] != run { |
| 25 | + return Err("clang_placeholder_origin_invalid"); |
| 26 | + } |
| 27 | + let expected=serde_json::to_vec_pretty(&json!({"schema_version":"0.1.0","workspace_id":r["workspace_id"],"run_id":run,"report_sha256":hash})).map_err(|_|"clang_placeholder_marker_invalid")?; |
| 28 | + if super::read_bounded( |
| 29 | + &root.join(format!(".codeguard/state/consumed/{run}.json")), |
| 30 | + 4096, |
| 31 | + )? != expected |
| 32 | + { |
| 33 | + return Err("clang_placeholder_observation_not_consumed"); |
| 34 | + } |
| 35 | + Ok(r) |
| 36 | +} |
| 37 | +/// 创建收据绑定的诊断指引;专用验证/尝试未接入时不授予允许修改范围。 |
| 38 | +pub(super) fn candidate(root: &Path, id: &str, fact: &Value) -> Result<Candidate, &'static str> { |
| 39 | + let run = fact["first_run_id"].as_str().ok_or("finding_run_invalid")?; |
| 40 | + let digest = fact["first_report_sha256"] |
| 41 | + .as_str() |
| 42 | + .ok_or("finding_report_invalid")?; |
| 43 | + let first = read(root, run, Some(digest))?; |
| 44 | + let fp = fingerprint(&first); |
| 45 | + if id != format!("CG-{}", &fp[..32]) |
| 46 | + || fact["fingerprint"] != fp |
| 47 | + || fact["kind"] != "finding" |
| 48 | + || fact["native_rule_id"] != RULE |
| 49 | + || fact["checker_id"] != checker(&first) |
| 50 | + || fact["path"] != first["path"] |
| 51 | + || fact["workspace_id"] != first["workspace_id"] |
| 52 | + || fact["first_source_sha256"] != first["source_sha256"] |
| 53 | + || positions(&first).is_empty() |
| 54 | + { |
| 55 | + return Err("clang_placeholder_fact_conflict"); |
| 56 | + } |
| 57 | + let score = |r: &Value| { |
| 58 | + r["run_id"] |
| 59 | + .as_str() |
| 60 | + .and_then(|s| s.rsplit('-').next()) |
| 61 | + .and_then(|n| n.parse::<u128>().ok()) |
| 62 | + .unwrap_or(0) |
| 63 | + }; |
| 64 | + let mut latest = first.clone(); |
| 65 | + let entries = fs::read_dir(root.join(".codeguard/reports")) |
| 66 | + .map_err(|_| "reports_unreadable")? |
| 67 | + .take(1001) |
| 68 | + .collect::<Result<Vec<_>, _>>() |
| 69 | + .map_err(|_| "reports_unreadable")?; |
| 70 | + if entries.len() > 1000 { |
| 71 | + return Err("clang_placeholder_history_limit"); |
| 72 | + } |
| 73 | + for entry in entries { |
| 74 | + let path = entry.path(); |
| 75 | + let Some(run) = path |
| 76 | + .file_stem() |
| 77 | + .and_then(|s| s.to_str()) |
| 78 | + .filter(|s| s.starts_with("clangdocplaceholder-")) |
| 79 | + else { |
| 80 | + continue; |
| 81 | + }; |
| 82 | + if !root |
| 83 | + .join(format!(".codeguard/state/consumed/{run}.json")) |
| 84 | + .exists() |
| 85 | + { |
| 86 | + continue; |
| 87 | + } |
| 88 | + let r = read(root, run, None)?; |
| 89 | + if r["workspace_id"] == first["workspace_id"] |
| 90 | + && r["path"] == first["path"] |
| 91 | + && r["language"] == first["language"] |
| 92 | + && score(&r) > score(&latest) |
| 93 | + { |
| 94 | + latest = r; |
| 95 | + } |
| 96 | + } |
| 97 | + let tool_current = first["selected_tool"] |
| 98 | + .as_str() |
| 99 | + .and_then(|p| Path::new(p).canonicalize().ok()) |
| 100 | + .and_then(|p| codeguard_runtime::read_bounded_regular_file(&p, 256 * 1024 * 1024).ok()) |
| 101 | + .is_some_and(|b| first["native"]["tool_sha256"] == format!("{:x}", Sha256::digest(b))); |
| 102 | + let context = latest["selected_tool"] == first["selected_tool"] |
| 103 | + && latest["standard"] == first["standard"] |
| 104 | + && latest["native"]["tool_sha256"] == first["native"]["tool_sha256"]; |
| 105 | + let status = if !context { |
| 106 | + "original_context_changed" |
| 107 | + } else if !tool_current || !current(root, &latest) { |
| 108 | + "input_changed" |
| 109 | + } else if positions(&latest).is_empty() { |
| 110 | + "candidate_absent_unverified_policy" |
| 111 | + } else { |
| 112 | + "placeholder_descriptions_observed" |
| 113 | + }; |
| 114 | + let observed = status == "placeholder_descriptions_observed"; |
| 115 | + let brief = json!({"schema_version":"0.34.0","task_id":id,"kind":"finding","checker_id":checker(&first),"scope":first["path"],"source_sha256":latest["source_sha256"],"placeholder_rule_id":RULE,"rule_source":"codeguard_structural_policy","evidence_ref":{"first_run_id":run,"first_report_sha256":digest},"current_run_id":latest["run_id"],"observation_status":status,"placeholder_positions":if observed {positions(&latest)}else{Vec::new()},"rule_basis":"整个用途、参数或适用返回说明仅为明确占位标记,不等于API契约说明;本规则不是Clang警告。","constraints":["不修改API或行为,不关闭检查器或自行批准白名单","专用尝试历史与task verify尚未接入,本视图不授予任务修复权限"],"allowed_paths":[],"affected_paths":[first["path"]],"disposition":"needs_decision","reason_code":if observed {"clang_placeholder_task_workflow_not_integrated"}else{status},"step":["依据当前组件与真实API核对占位说明;专用修复闭环未接入前保留任务。","使用绑定原工具、标准与工作区的comments命令诊断复扫;源码或工具失稳时不按历史定位修改。"],"recheck_argv":["codeguard","comments",first["language"],root.join(first["path"].as_str().ok_or("clang_placeholder_scope_invalid")?),"--clang-tool",first["selected_tool"],"--standard",first["standard"],"--workspace",root,"--format=json"],"attempt_history_status":"not_integrated","closure_condition":"原命令局部复扫不关闭任务;专用受控尝试、原工具task verify及可信关闭仍须实现并验收。","authority":"local_unverified","delivery_decision":"not_evaluated"}); |
| 116 | + Ok(Candidate { |
| 117 | + id: id.into(), |
| 118 | + priority: 0, |
| 119 | + brief, |
| 120 | + }) |
| 121 | +} |
0 commit comments