Repository navigation
持久化 TypeScript WASM 疑似位置并绑定原生确认任务 #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| //! ESLint 原生确认任务中的脱敏 WASM 疑似位置;只具待核实证据权威。 | ||
|
|
||
| use codeguard_adapters::bundled_grammar_candidates; | ||
| use serde_json::{Value, json}; | ||
| use sha2::{Digest, Sha256}; | ||
|
|
||
| /// 从当轮候选反馈提取有界坐标,不保存源码片段或语法节点文本。 | ||
| /// 参数为初检反馈、工作区相对路径和当前源码字节;返回绑定固定 grammar 的疑似证据。 | ||
| pub(crate) fn project(precheck: &Value, scope: &str, source: &[u8]) -> Result<Value, &'static str> { | ||
| let observations = precheck["observations"] | ||
| .as_array() | ||
| .filter(|rows| rows.len() <= 128) | ||
| .ok_or("syntax_evidence_observations_invalid")?; | ||
| let positions: Vec<Value> = observations | ||
| .iter() | ||
| .map(|row| { | ||
| json!({ | ||
| "classification":row["classification"], | ||
| "kind":row["kind"], | ||
| "start_line":row["start_line"], | ||
| "start_column":row["start_column"], | ||
| "end_line":row["end_line"], | ||
| "end_column":row["end_column"], | ||
| "group_id":row["group_id"] | ||
| }) | ||
| }) | ||
| .collect(); | ||
| let evidence = json!({ | ||
| "schema_version":"1.0.0", | ||
| "backend":"bundled_tree_sitter_wasm_candidate", | ||
| "language":precheck["language"], | ||
| "scope":scope, | ||
| "source_sha256":precheck["source_sha256"], | ||
| "grammar_sha256":precheck["grammar_sha256"], | ||
| "grammar_qualified":precheck["grammar_qualified"], | ||
| "precheck_reason":precheck["reason"], | ||
| "truncated":precheck["truncated"], | ||
| "observations":positions | ||
| }); | ||
| if !valid(&evidence, scope, source) { | ||
| return Err("syntax_evidence_identity_or_position_invalid"); | ||
| } | ||
| Ok(evidence) | ||
| } | ||
|
|
||
| /// 核对当前源码、固定 grammar 身份和每个 Unicode 标量坐标。 | ||
| /// 参数为不可信报告投影、工作区相对路径及当前源码;返回能否作为未核实任务证据。 | ||
| pub(crate) fn valid(evidence: &Value, scope: &str, source: &[u8]) -> bool { | ||
| let keys = [ | ||
| "schema_version", | ||
| "backend", | ||
| "language", | ||
| "scope", | ||
| "source_sha256", | ||
| "grammar_sha256", | ||
| "grammar_qualified", | ||
| "precheck_reason", | ||
| "truncated", | ||
| "observations", | ||
| ]; | ||
| let Some(rows) = evidence["observations"] | ||
| .as_array() | ||
| .filter(|rows| rows.len() <= 128) | ||
| else { | ||
| return false; | ||
| }; | ||
| let Some(language) = evidence["language"].as_str() else { | ||
| return false; | ||
| }; | ||
| if !evidence.as_object().is_some_and(|object| { | ||
| object.len() == keys.len() && keys.iter().all(|key| object.contains_key(*key)) | ||
| }) || evidence["schema_version"] != "1.0.0" | ||
| || evidence["backend"] != "bundled_tree_sitter_wasm_candidate" | ||
| || evidence["scope"] != scope | ||
| || evidence["source_sha256"] != format!("{:x}", Sha256::digest(source)) | ||
| || evidence["grammar_qualified"] != false | ||
| || !matches_language(scope, language) | ||
| || !evidence["truncated"].is_boolean() | ||
| || (evidence["truncated"] == true | ||
| && evidence["precheck_reason"] != "recovery_budget_truncated") | ||
| || (evidence["truncated"] == false | ||
| && evidence["precheck_reason"] != "grammar_version_unqualified") | ||
| { | ||
| return false; | ||
| } | ||
| let Ok(manifest) = bundled_grammar_candidates() else { | ||
| return false; | ||
| }; | ||
| if !manifest.assets.iter().any(|asset| { | ||
| asset.language == language | ||
| && asset.sha256 == evidence["grammar_sha256"] | ||
| && asset.release_status == "candidate_unvalidated" | ||
| }) { | ||
| return false; | ||
| } | ||
| let Ok(text) = std::str::from_utf8(source) else { | ||
| return false; | ||
| }; | ||
| let lines: Vec<&str> = text.split('\n').collect(); | ||
| rows.iter().all(|row| valid_position(row, &lines)) | ||
| } | ||
|
|
||
| fn matches_language(scope: &str, language: &str) -> bool { | ||
| match language { | ||
| "typescript" => [".ts", ".mts", ".cts"] | ||
| .iter() | ||
| .any(|suffix| scope.ends_with(suffix)), | ||
| "tsx" => scope.ends_with(".tsx"), | ||
| _ => false, | ||
| } | ||
| } | ||
|
|
||
| fn valid_position(row: &Value, lines: &[&str]) -> bool { | ||
| let keys = [ | ||
| "classification", | ||
| "kind", | ||
| "start_line", | ||
| "start_column", | ||
| "end_line", | ||
| "end_column", | ||
| "group_id", | ||
| ]; | ||
| let (Some(start_line), Some(start_column), Some(end_line), Some(end_column), Some(group_id)) = ( | ||
| row["start_line"].as_u64(), | ||
| row["start_column"].as_u64(), | ||
| row["end_line"].as_u64(), | ||
| row["end_column"].as_u64(), | ||
| row["group_id"].as_u64(), | ||
| ) else { | ||
| return false; | ||
| }; | ||
| row.as_object().is_some_and(|object| { | ||
| object.len() == keys.len() && keys.iter().all(|key| object.contains_key(*key)) | ||
| }) && row["classification"] == "suspected" | ||
| && matches!(row["kind"].as_str(), Some("ERROR" | "MISSING")) | ||
| && group_id > 0 | ||
| && start_line > 0 | ||
| && end_line >= start_line | ||
| && end_line <= lines.len() as u64 | ||
| && start_column > 0 | ||
| && end_column > 0 | ||
| && start_column <= lines[start_line as usize - 1].chars().count() as u64 + 1 | ||
| && end_column <= lines[end_line as usize - 1].chars().count() as u64 + 1 | ||
| && (start_line, start_column) <= (end_line, end_column) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -452,6 +452,10 @@ fn import_one( | |
| codeguard_adapters::parse_unique_json(&bytes) | ||
| .map_err(|_| "npm_report_duplicate_or_invalid_json")?; | ||
| } | ||
| if value["report_type"] == "eslint_preparation_observation" { | ||
| codeguard_adapters::parse_unique_json(&bytes) | ||
| .map_err(|_| "eslint_preparation_duplicate_or_invalid_json")?; | ||
| } | ||
| // 已消费的历史报告按原字节收据确认,不用当前源码重演历史输入。 | ||
| if matches!( | ||
| value["report_type"].as_str(), | ||
|
|
@@ -2030,6 +2034,14 @@ fn render_blocker_task(report: &ReportInput, blocker: &BlockerInput) -> String { | |
| blocker.id, blocker.reason, report.digest, report.run_id | ||
| ); | ||
| } | ||
| if blocker.checker_id == "node.eslint.preparation" | ||
| && blocker.diagnostic_reason.as_deref() == Some("eslint_syntax_confirmation_needed") | ||
| { | ||
| return format!( | ||
| "# {} TypeScript 语法原生确认任务\n\n- 问题证据:源码范围 `{}` 的候选 WASM 初检尚未验收;脱敏疑似位置、源码及 grammar 摘要在 `.codeguard/reports/{}.json` 的 `syntax_evidence`,报告 SHA-256 `{}`。这些不是已确认源码违规。\n- 规则依据:Tree-sitter 恢复节点只提示疑似语法位置;原生 TypeScript/ESLint 能力和项目原配置仍须确认。\n- 允许范围:核对本源码、对应构建根、原生工具及配置;不得仅凭 WASM 恢复节点修改源码、增加忽略或白名单。\n- 修复步骤:先查看同 run 的脱敏位置,再恢复适用的原生语法检查;若原生反证,保留证据并进入 grammar 误报调查。\n- 复检命令:codeguard lint typescript <原源码> --workspace <原工作区> --node-tool <核验绝对路径> --eslint-entry <核验绝对路径> --eslint-version <核验版本> --config <原配置绝对路径> --cwd <原工作目录绝对路径>。\n- 历史尝试:首次 run `{}`;后续观察、尝试和复检保留在同一任务,首张 Markdown 不是完整历史。\n- 关闭条件:同输入、范围、方言及语法能力的原生复检和正式策略/覆盖核验均满足;安装、WASM 零恢复或任务勾选不能关闭。\n", | ||
|
Comment on lines
+2037
to
+2041
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this source-scoped blocker already exists—for example after an earlier Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a TypeScript filename contains Markdown or HTML metacharacters such as a backtick, Useful? React with 👍 / 👎. |
||
| blocker.id, blocker.scope, report.run_id, report.digest, report.run_id | ||
| ); | ||
| } | ||
| if blocker.checker_id == "node.eslint.preparation" { | ||
| return format!( | ||
| "# {} ESLint 检查完整性任务\n\n- 问题证据:诊断 `{}`,报告摘要 `{}`。\n- 规则依据:原工具、配置和报告检查完整性;不是已确认源码违规。\n- 允许范围:核对Node、ESLint入口/版本、原配置、parser/插件及报告归属;不修改无关源码。\n- 修复步骤:恢复缺失上下文;若为解析或抑制诊断,先复现并定位根因,不默认添加忽略或白名单。\n- 复检:codeguard lint typescript <原源码> --workspace <原工作区> --node-tool <核验绝对路径> --eslint-entry <核验绝对路径> --eslint-version <核验版本> --config <原配置绝对路径> --cwd <原工作目录绝对路径>。\n- 历史尝试:首次run `{}`,后续诊断保留在同一任务观察中。\n- 关闭条件:本来受阻的原检查恢复并完成正式复检;任务勾选和局部成功不能关闭。\n", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For an imported 0.2.0 report, the source and grammar hashes are checked, but each observation is accepted solely because its coordinates are in bounds. A report can therefore retain the genuine hashes, change an observation to any other in-bounds line and column, and pass
work sync;nextthen presents that fabricated location as suspected Tree-sitter evidence. Recompute the recovery nodes with the identified grammar, or otherwise verify a worker-derived binding for the observation set, rather than treating coordinate bounds as provenance.Useful? React with 👍 / 👎.