Skip to content

Commit a30f3ef

Browse files
committed
持久化占位复检失败诊断并撤回历史修复权限
1 parent 62f2189 commit a30f3ef

23 files changed

Lines changed: 1554 additions & 41 deletions

‎crates/codeguard-cli/src/c_family_placeholder_task_recheck.rs‎

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const EXTRA: &[&str] = &[
1515
"task_rule",
1616
"original_ref",
1717
"input_stable",
18+
"placeholder_observation_status",
19+
"placeholder_observation_reason",
1820
];
1921

2022
fn reference(brief: &Value) -> Value {
@@ -184,11 +186,23 @@ pub(crate) fn run(root: &Path, brief: &Value, deadline: Instant) -> Result<Value
184186
structure["reason"] = json!("clang_execution_incomplete");
185187
structure["observation"] = Value::Null;
186188
}
187-
if !complete || structure["status"] != "observed" || placeholders["status"] != "observed" {
188-
return Err("clang_placeholder_recheck_incomplete");
189-
}
190189
let mut r = first.clone();
191-
r["placeholders"] = placeholders["observation"].clone();
190+
let observed =
191+
complete && structure["status"] == "observed" && placeholders["status"] == "observed";
192+
r["schema_version"] = json!("0.2.0");
193+
r["placeholder_observation_status"] = json!(if observed { "observed" } else { "incomplete" });
194+
r["placeholder_observation_reason"] = if observed {
195+
json!("clang_placeholder_observed")
196+
} else if !complete {
197+
json!("clang_execution_incomplete")
198+
} else {
199+
placeholders["reason"].clone()
200+
};
201+
r["placeholders"] = if observed {
202+
placeholders["observation"].clone()
203+
} else {
204+
Value::Null
205+
};
192206
r["report_type"] = json!("clang_documentation_placeholder_task_recheck");
193207
r["run_id"] = json!(format!(
194208
"clangdocplaceholder-{}-{}-{}",
@@ -206,7 +220,7 @@ pub(crate) fn run(root: &Path, brief: &Value, deadline: Instant) -> Result<Value
206220
r["task_kind"] = json!("finding");
207221
r["task_rule"] = json!(contract::RULE);
208222
r["original_ref"] = reference(brief);
209-
r["input_stable"] = json!(contract::current(root, &normal(&r)) && tool_current(&first));
223+
r["input_stable"] = json!(normal_inputs_current(root, &normal(&r)) && tool_current(&first));
210224
if !valid_shape(root, &r) {
211225
return Err("clang_placeholder_recheck_invalid");
212226
}
@@ -219,23 +233,74 @@ pub(crate) fn normal(r: &Value) -> Value {
219233
for k in EXTRA {
220234
o.remove(*k);
221235
}
236+
o.insert("schema_version".into(), json!("0.1.0"));
222237
o.insert(
223238
"report_type".into(),
224239
json!("clang_documentation_placeholder_workbench_observation"),
225240
);
226241
}
227242
n
228243
}
244+
/// 校验规范化的当前观察;失败只能携带空占位数据,不会成为干净候选。
245+
pub(crate) fn valid_normal_shape(r: &Value) -> bool {
246+
if r["placeholders"].is_null() {
247+
r.is_object()
248+
&& r["report_type"] == "clang_documentation_placeholder_workbench_observation"
249+
&& r["profile"] == "clang-documentation-placeholder-v1"
250+
&& r["run_id"]
251+
.as_str()
252+
.is_some_and(|s| s.starts_with("clangdocplaceholder-"))
253+
&& crate::work_sync::c_family_structure_report::valid_shape(
254+
&contract::structure_packet(r),
255+
)
256+
} else {
257+
contract::valid_shape(r)
258+
}
259+
}
260+
/// 检查失败观察的源码与底层结构关联;没有占位数据时不验证任何旧占位位置。
261+
pub(crate) fn normal_inputs_current(root: &Path, r: &Value) -> bool {
262+
valid_normal_shape(r)
263+
&& if r["placeholders"].is_null() {
264+
crate::work_sync::c_family_structure_report::current(
265+
root,
266+
&contract::structure_packet(r),
267+
)
268+
} else {
269+
contract::current(root, r)
270+
}
271+
}
229272
/// 绑定首次报告、原策略及开放任务;拒绝额外字段与身份篡改。
230273
pub(crate) fn valid_shape(root: &Path, r: &Value) -> bool {
231-
if !r
232-
.as_object()
233-
.is_some_and(|o| o.len() == 23 && EXTRA.iter().all(|k| o.contains_key(*k)))
234-
|| r["report_type"] != "clang_documentation_placeholder_task_recheck"
274+
if !r.as_object().is_some_and(|o| {
275+
if r["schema_version"] == "0.1.0" {
276+
o.len() == 23 && EXTRA[..5].iter().all(|k| o.contains_key(*k))
277+
} else {
278+
r["schema_version"] == "0.2.0"
279+
&& o.len() == 25
280+
&& EXTRA.iter().all(|k| o.contains_key(*k))
281+
}
282+
}) || r["report_type"] != "clang_documentation_placeholder_task_recheck"
235283
|| r["task_kind"] != "finding"
236284
|| r["task_rule"] != contract::RULE
237285
|| !r["input_stable"].is_boolean()
238-
|| !contract::valid_shape(&normal(r))
286+
|| (r["schema_version"] == "0.1.0" && !contract::valid_shape(&normal(r)))
287+
|| !valid_normal_shape(&normal(r))
288+
|| (r["schema_version"] == "0.2.0"
289+
&& !((r["placeholder_observation_status"] == "observed"
290+
&& r["placeholder_observation_reason"] == "clang_placeholder_observed"
291+
&& contract::valid_shape(&normal(r)))
292+
|| (r["placeholder_observation_status"] == "incomplete"
293+
&& r["placeholders"].is_null()
294+
&& matches!(
295+
r["placeholder_observation_reason"].as_str(),
296+
Some(
297+
"clang_placeholder_unavailable"
298+
| "clang_placeholder_report_invalid"
299+
| "clang_source_changed"
300+
| "request_cancelled"
301+
| "clang_execution_incomplete"
302+
)
303+
))))
239304
{
240305
return false;
241306
}
@@ -270,16 +335,17 @@ pub(crate) fn valid_shape(root: &Path, r: &Value) -> bool {
270335
/// 持久化前重新核对当前源码和原工具,防止竞态变成完整复检。
271336
pub(crate) fn inputs_current(root: &Path, r: &Value) -> bool {
272337
valid_shape(root, r)
273-
&& contract::current(root, &normal(r))
338+
&& normal_inputs_current(root, &normal(r))
274339
&& original(root, &r["original_ref"]).is_ok_and(|first| tool_current(&first))
275-
&& tool_current(r)
340+
&& (r["native"]["tool_sha256"].is_null() || tool_current(r))
276341
}
277342
/// 分类占位组件局部结果,未知或失稳不得变成消失或可信关闭。
278343
pub(crate) fn classify(brief: &Value, r: &Value) -> &'static str {
279344
if r["task_id"] != brief["task_id"]
280345
|| r["input_stable"] != true
281346
|| r["local_scan_complete"] != true
282347
|| r["structure"]["status"] != "observed"
348+
|| r["placeholders"].is_null()
283349
{
284350
"incomplete"
285351
} else if contract::positions(&normal(r)).is_empty() {

‎crates/codeguard-cli/src/next_command.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2445,7 +2445,7 @@ fn run_sequence(run_id: &str) -> Option<u128> {
24452445

24462446
fn view(disposition: &str, reason: &str, brief: Value, actions: Value) -> Value {
24472447
json!({
2448-
"schema_version":if brief["schema_version"] == "0.35.0" {json!("0.35.0")}else if brief["schema_version"] == "0.34.0" {json!("0.34.0")}else if brief["schema_version"] == "0.33.0" {json!("0.33.0")}else if brief["schema_version"] == "0.32.0" {json!("0.32.0")}else if brief["schema_version"] == "0.31.0" {json!("0.31.0")}else if brief["schema_version"] == "0.30.0" {json!("0.30.0")}else if brief["schema_version"] == "0.29.0" {json!("0.29.0")}else if brief["schema_version"] == "0.28.0" {json!("0.28.0")}else if brief["schema_version"] == "0.27.0" {json!("0.27.0")}else if brief["schema_version"] == "0.26.0" {json!("0.26.0")}else if brief["schema_version"] == "0.25.0" {json!("0.25.0")}else if brief["schema_version"] == "0.24.0" {json!("0.24.0")}else if brief["schema_version"] == "0.23.0" {json!("0.23.0")}else if brief["schema_version"] == "0.22.0" {json!("0.22.0")}else if brief["schema_version"] == "0.21.0" {json!("0.21.0")}else if brief["schema_version"] == "0.20.0" {json!("0.20.0")}else if brief["checker_id"] == "shell.shellcheck" {json!("0.17.0")} else if brief["checker_id"] == "go.vet" {json!("0.13.0")} else if brief["checker_id"] == "syntax.native_confirmation" {brief["schema_version"].clone()} else {json!("0.1.0")}, "report_type":"repair_brief_preview",
2448+
"schema_version":if brief["schema_version"] == "0.36.0" {json!("0.36.0")}else if brief["schema_version"] == "0.35.0" {json!("0.35.0")}else if brief["schema_version"] == "0.34.0" {json!("0.34.0")}else if brief["schema_version"] == "0.33.0" {json!("0.33.0")}else if brief["schema_version"] == "0.32.0" {json!("0.32.0")}else if brief["schema_version"] == "0.31.0" {json!("0.31.0")}else if brief["schema_version"] == "0.30.0" {json!("0.30.0")}else if brief["schema_version"] == "0.29.0" {json!("0.29.0")}else if brief["schema_version"] == "0.28.0" {json!("0.28.0")}else if brief["schema_version"] == "0.27.0" {json!("0.27.0")}else if brief["schema_version"] == "0.26.0" {json!("0.26.0")}else if brief["schema_version"] == "0.25.0" {json!("0.25.0")}else if brief["schema_version"] == "0.24.0" {json!("0.24.0")}else if brief["schema_version"] == "0.23.0" {json!("0.23.0")}else if brief["schema_version"] == "0.22.0" {json!("0.22.0")}else if brief["schema_version"] == "0.21.0" {json!("0.21.0")}else if brief["schema_version"] == "0.20.0" {json!("0.20.0")}else if brief["checker_id"] == "shell.shellcheck" {json!("0.17.0")} else if brief["checker_id"] == "go.vet" {json!("0.13.0")} else if brief["checker_id"] == "syntax.native_confirmation" {brief["schema_version"].clone()} else {json!("0.1.0")}, "report_type":"repair_brief_preview",
24492449
"operation":"next", "command_status":"complete", "exit_code":0,
24502450
"disposition":disposition, "reason":reason,
24512451
"repair_brief":brief, "next_actions":actions,

‎crates/codeguard-cli/src/next_command/c_family_placeholder_candidate.rs‎

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! 占位文件任务绑定首次报告、收据、当前输入及受控尝试;局部复检不授予可信关闭。
22
use super::Candidate;
33
use crate::work_sync::c_family_placeholder_report::{
4-
checker, current, fingerprint, positions, valid_shape, RULE,
4+
checker, fingerprint, positions, valid_shape, RULE,
55
};
66
use serde_json::{Value, json};
77
use sha2::{Digest, Sha256};
@@ -21,13 +21,19 @@ fn read(root: &Path, run: &str, digest: Option<&str>) -> Result<Value, &'static
2121
}
2222
let mut r = codeguard_adapters::parse_unique_json(&bytes)
2323
.map_err(|_| "clang_placeholder_origin_invalid")?;
24-
if r["report_type"] == "clang_documentation_placeholder_task_recheck" {
24+
let recheck = r["report_type"] == "clang_documentation_placeholder_task_recheck";
25+
if recheck {
2526
if !crate::c_family_placeholder_task_recheck::valid_shape(root, &r) {
2627
return Err("clang_placeholder_recheck_invalid");
2728
}
2829
r = crate::c_family_placeholder_task_recheck::normal(&r);
2930
}
30-
if !valid_shape(&r) || r["run_id"] != run {
31+
if !(if recheck {
32+
crate::c_family_placeholder_task_recheck::valid_normal_shape(&r)
33+
} else {
34+
valid_shape(&r)
35+
}) || r["run_id"] != run
36+
{
3137
return Err("clang_placeholder_origin_invalid");
3238
}
3339
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")?;
@@ -107,11 +113,16 @@ pub(super) fn candidate(root: &Path, id: &str, fact: &Value) -> Result<Candidate
107113
.is_some_and(|b| first["native"]["tool_sha256"] == format!("{:x}", Sha256::digest(b)));
108114
let context = latest["selected_tool"] == first["selected_tool"]
109115
&& latest["standard"] == first["standard"]
110-
&& latest["native"]["tool_sha256"] == first["native"]["tool_sha256"];
116+
&& (latest["native"]["tool_sha256"] == first["native"]["tool_sha256"]
117+
|| (latest["placeholders"].is_null() && latest["native"]["tool_sha256"].is_null()));
111118
let status = if !context {
112119
"original_context_changed"
113-
} else if !tool_current || !current(root, &latest) {
120+
} else if !tool_current
121+
|| !crate::c_family_placeholder_task_recheck::normal_inputs_current(root, &latest)
122+
{
114123
"input_changed"
124+
} else if latest["placeholders"].is_null() {
125+
"native_placeholder_incomplete"
115126
} else if positions(&latest).is_empty() {
116127
"candidate_absent_unverified_policy"
117128
} else {
@@ -128,7 +139,13 @@ pub(super) fn candidate(root: &Path, id: &str, fact: &Value) -> Result<Candidate
128139
first["selected_tool"],
129140
"--format=json"
130141
]);
131-
let mut brief = json!({"schema_version":"0.35.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 actionable {positions(&latest)}else{Vec::new()},"rule_basis":"整个用途、参数或适用返回说明仅为明确占位标记,不等于API契约说明;本规则不是Clang警告或完整语义准确性证明。","constraints":["仅修改当前文件文档注释,保留API及行为","以当前字节位置核对各占位组件,不按函数名字单独匹配","不关闭检查器、不以占位文本或白名单自批代替修复"],"allowed_paths":if actionable {json!([first["path"]])}else{json!([])},"affected_paths":[first["path"]],"disposition":if actionable {"actionable"}else{"needs_decision"},"reason_code":status,"step":["按所有当前函数字节/行列证据,依据真实声明和行为替换占位组件为真实用途、契约和行为说明。","使用绑定原工具、标准与工作区的task verify复检;未知或失稳时先诊断,不按旧位置改源码。"],"recheck_argv":argv,"closure_condition":"原命令局部复扫不关闭;专用task verify记录局部观察;仍须跨输入语义无进展、完整详细准确性/项目覆盖与可信关闭/复发验收。","authority":"local_unverified","delivery_decision":"not_evaluated"});
142+
let mut brief = json!({"schema_version":"0.36.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 actionable {positions(&latest)}else{Vec::new()},"rule_basis":"整个用途、参数或适用返回说明仅为明确占位标记,不等于API契约说明;本规则不是Clang警告或完整语义准确性证明。","constraints":["仅修改当前文件文档注释,保留API及行为","以当前字节位置核对各占位组件,不按函数名字单独匹配","不关闭检查器、不以占位文本或白名单自批代替修复"],"allowed_paths":if actionable {json!([first["path"]])}else{json!([])},"affected_paths":[first["path"]],"disposition":if actionable {"actionable"}else{"needs_decision"},"reason_code":status,"step":["按所有当前函数字节/行列证据,依据真实声明和行为替换占位组件为真实用途、契约和行为说明。","使用绑定原工具、标准与工作区的task verify复检;未知或失稳时先诊断,不按旧位置改源码。"],"recheck_argv":argv,"closure_condition":"原命令局部复扫不关闭;专用task verify记录局部观察;仍须跨输入语义无进展、完整详细准确性/项目覆盖与可信关闭/复发验收。","authority":"local_unverified","delivery_decision":"not_evaluated"});
143+
if status == "native_placeholder_incomplete" {
144+
brief["step"] = json!([
145+
"读取current_run_id绑定的native诊断和占位失败原因,先处理具体检查阻塞;语法诊断使用对应语法修复流程。",
146+
"恢复原工具检查条件并执行task verify;不得按历史占位定位修改源码,不得关闭检查器或自批白名单。"
147+
]);
148+
}
132149
brief["action_id"] = json!(super::canonical_action_id(&brief)?);
133150
let history = crate::task_attempt_command::attempt_history(root, id, &brief)?;
134151
let mut priority = if actionable { 1 } else { 0 };

‎crates/codeguard-cli/src/task_verify_command.rs‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ pub fn run(args: &[String]) -> ExitCode {
971971
});
972972
}
973973
if c_placeholder_task {
974-
report["schema_version"] = json!("0.38.0");
974+
report["schema_version"] = json!("0.39.0");
975975
}
976976
if c_structure_task {
977977
report["schema_version"] = json!("0.37.0");
@@ -1038,7 +1038,11 @@ pub fn run(args: &[String]) -> ExitCode {
10381038
}
10391039
let persist = if codeguard_runtime::sigint_cancellation_requested() {
10401040
Err("request_cancelled")
1041-
} else if Instant::now() >= deadline {
1041+
} else if Instant::now() >= deadline
1042+
&& !(c_placeholder_task
1043+
&& scan["schema_version"] == "0.2.0"
1044+
&& scan["placeholder_observation_status"] == "incomplete")
1045+
{
10421046
Err("request_deadline_exceeded")
10431047
} else {
10441048
match lock_verification(&root, &parsed.task_id, &lease) {

‎crates/codeguard-cli/src/work_sync.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,23 @@ fn parse_report(
635635
if !crate::c_family_placeholder_task_recheck::valid_shape(root, report) {
636636
return Err("clang_placeholder_recheck_invalid");
637637
}
638+
if report["placeholders"].is_null() {
639+
let run = report["run_id"].as_str().ok_or("report_run_id_invalid")?;
640+
if report["workspace_id"] != workspace_id
641+
|| path.file_stem().and_then(|s| s.to_str()) != Some(run)
642+
{
643+
return Err("clang_placeholder_recheck_invalid");
644+
}
645+
// 不完整复检只消费脱敏诊断和事件,不制造结构/占位违规,也不关闭原事实。
646+
return Ok(ReportInput {
647+
workspace_id: workspace_id.into(),
648+
run_id: run.into(),
649+
digest,
650+
findings: Vec::new(),
651+
blockers: Vec::new(),
652+
historical_findings: 0,
653+
});
654+
}
638655
return c_family_placeholder_report::parse(
639656
root,
640657
workspace_id,

‎crates/codeguard-cli/src/work_sync/c_family_placeholder_report.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};
77
/// 占位描述自有规则,与原生警告及缺失组件策略保持不同身份。
88
pub(crate) const RULE: &str = "codeguard.documentation.placeholder_description";
99

10-
fn structure_packet(report: &Value) -> Value {
10+
pub(crate) fn structure_packet(report: &Value) -> Value {
1111
let mut packet = report.clone();
1212
if let Some(object) = packet.as_object_mut() {
1313
object.remove("placeholders");

‎crates/codeguard-cli/src/workspace_view_command.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ pub fn run_show(args: &[String]) -> ExitCode {
165165
Some("c.clang.documentation" | "cpp.clang.documentation")
166166
);
167167
// 首次原生确认必须保留简报绑定的工具选择参数,避免外层动作丢失必要输入。
168-
let next_actions = if brief["schema_version"] == "0.35.0"
168+
let next_actions = if matches!(brief["schema_version"].as_str(), Some("0.35.0" | "0.36.0"))
169169
|| c_structure
170170
|| c_documentation
171171
|| brief["checker_id"] == "shell.shellcheck"
@@ -178,7 +178,7 @@ pub fn run_show(args: &[String]) -> ExitCode {
178178
json!([["codeguard", "task", "verify", id, "."]])
179179
};
180180
let report = json!({
181-
"schema_version":if brief["schema_version"] == "0.35.0" {"0.8.0"}else if c_structure {"0.7.0"}else if c_documentation {"0.4.0"}else if brief["checker_id"] == "shell.shellcheck" {"0.3.0"}else if brief["schema_version"] == "0.12.0" {"0.2.0"}else{"0.1.0"},"report_type":"task_show_preview",
181+
"schema_version":if brief["schema_version"] == "0.36.0" {"0.9.0"}else if brief["schema_version"] == "0.35.0" {"0.8.0"}else if c_structure {"0.7.0"}else if c_documentation {"0.4.0"}else if brief["checker_id"] == "shell.shellcheck" {"0.3.0"}else if brief["schema_version"] == "0.12.0" {"0.2.0"}else{"0.1.0"},"report_type":"task_show_preview",
182182
"operation":"task_show","request_id":request_id(),"command_status":"complete","exit_code":0,
183183
"task_id":id,"task":brief,"state":"open",
184184
"evidence_freshness":"unverified","event_chain_status":"unverified",

0 commit comments

Comments
 (0)