已合并
fix(governance): 按 checkout 实际文件类型校验本地源码导航 #65
fix(governance): 按 checkout 实际文件类型校验本地源码导航 #65
已合并
bububu创建于 9月26日
共 3 个文件变更+47-3
@@ -52,7 +52,11 @@ class SourceResourceTests(unittest.TestCase):
52 self.raw = "// AscendC::GetBlockIdx();\nint kernel() {\n return AscendC::GetBlockIdx();\n}\n"52 self.raw = "// AscendC::GetBlockIdx();\nint kernel() {\n return AscendC::GetBlockIdx();\n}\n"
53 (self.checkout / "kernel.h").write_text(self.raw)53 (self.checkout / "kernel.h").write_text(self.raw)
54 (self.checkout / "notes.md").write_text("# source notes\n")54 (self.checkout / "notes.md").write_text("# source notes\n")
55- self.git("add", "kernel.h", "notes.md")55+ (self.checkout / "src").mkdir()
56+ (self.checkout / "src/Makefile").write_text("all:\n\t@true\n")
57+ (self.checkout / "src/fake.h").mkdir()
58+ (self.checkout / "src/fake.h/marker").write_text("tracked directory fixture\n")
59+ self.git("add", "kernel.h", "notes.md", "src/Makefile", "src/fake.h/marker")
56 self.git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "--quiet", "-m", "fixture")60 self.git("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", "commit", "--quiet", "-m", "fixture")
57 self.revision = self.git("rev-parse", "HEAD")61 self.revision = self.git("rev-parse", "HEAD")
58 destination = self.checkout.with_name(self.revision)62 destination = self.checkout.with_name(self.revision)
@@ -94,6 +98,21 @@ class SourceResourceTests(unittest.TestCase):
94 sources = [f"cann-ops-raw/ascendc/ops-nn/{self.revision}/kernel.h"]98 sources = [f"cann-ops-raw/ascendc/ops-nn/{self.revision}/kernel.h"]
95 return self.card(f"knowledge/ops/ascendc/operators/aclnn/{family}/fixture/op_{number}.md", body, title=f"Operator {number}", sources=sources)99 return self.card(f"knowledge/ops/ascendc/operators/aclnn/{family}/fixture/op_{number}.md", body, title=f"Operator {number}", sources=sources)
96 100 
101+ def local_tree_navigation_issues(self, linked_path):
102+ source = f"{self.definition['root']}/{self.revision}/src"
103+ target = f"{self.definition['root']}/{self.revision}/{linked_path}"
104+ target = os.path.relpath(self.root / target, self.root / "knowledge")
105+ document = SimpleNamespace(
106+ path=self.root / "knowledge/card.md",
107+ metadata={"sources": [{"resource": source}]},
108+ body=f"[source]({target})",
109+ )
110+ context = SimpleNamespace(strict_navigation=True)
111+ checker = importlib.import_module(
112+ f"{api_links.ingest.contract_package().__name__}.checks.source_navigation"
113+ )
114+ return checker.check(self.contract, document, context)
115+ 
97 def test_remote_variants_and_local_paths_keep_identical_revision(self):116 def test_remote_variants_and_local_paths_keep_identical_revision(self):
98 expected = f"cann-ops-raw/ascendc/ops-nn/{self.revision}/kernel.h"117 expected = f"cann-ops-raw/ascendc/ops-nn/{self.revision}/kernel.h"
99 for spelling in (f"https://gitcode.com/cann/ops-nn/blob/{self.revision}/kernel.h", f"https://gitcode.com/cann/ops-nn/-/blob/{self.revision}/kernel%2Eh", expected):118 for spelling in (f"https://gitcode.com/cann/ops-nn/blob/{self.revision}/kernel.h", f"https://gitcode.com/cann/ops-nn/-/blob/{self.revision}/kernel%2Eh", expected):
@@ -183,6 +202,15 @@ class SourceResourceTests(unittest.TestCase):
183 self.assertTrue(source_checks.is_safe_local_resource(self.contract, resource))202 self.assertTrue(source_checks.is_safe_local_resource(self.contract, resource))
184 self.assertFalse(source_checks.is_safe_local_resource(self.contract, resource.replace(self.revision, "a" * 40)))203 self.assertFalse(source_checks.is_safe_local_resource(self.contract, resource.replace(self.revision, "a" * 40)))
185 204 
205+ def test_strict_navigation_uses_restored_file_types_for_local_sources(self):
206+ self.assertEqual([], self.local_tree_navigation_issues("src/Makefile"))
207+ issues = self.local_tree_navigation_issues("src/fake.h")
208+ self.assertEqual(["source.navigation"], [issue.code for issue in issues])
209+ 
210+ def test_strict_navigation_skips_local_file_type_checks_when_checkout_is_offline(self):
211+ (self.root / self.definition["root"]).rename(self.root / "offline-backup")
212+ self.assertEqual([], self.local_tree_navigation_issues("src/Makefile"))
213+ 
186 def test_fetch_creates_nested_parents_without_following_ancestor_symlinks(self):214 def test_fetch_creates_nested_parents_without_following_ancestor_symlinks(self):
187 definition = {**self.definition, "root": "new-raw/ascendc/ops-nn"}215 definition = {**self.definition, "root": "new-raw/ascendc/ops-nn"}
188 with (216 with (
@@ -55,15 +55,17 @@ def check(contract: Contract, document, context) -> list[Issue]:
55 source = git_source(contract, resource)55 source = git_source(contract, resource)
56 if source is None:56 if source is None:
57 continue57 continue
58+ if not source.checkout(contract.root).exists():
59+ continue
58 target = source.local_path(contract.root)60 target = source.local_path(contract.root)
59- if not target.is_dir() and (target.exists() or "." in target.name):61+ if target.is_file() or not target.is_dir():
60 continue62 continue
61 locations = [git_source(contract, candidate) for candidate in resources]63 locations = [git_source(contract, candidate) for candidate in resources]
62 locations.extend(local_source_link(contract, document.path, candidate) for candidate in body_links)64 locations.extend(local_source_link(contract, document.path, candidate) for candidate in body_links)
63 if not any(65 if not any(
64 candidate is not None66 candidate is not None
65 and (candidate.root, candidate.revision) == (source.root, source.revision)67 and (candidate.root, candidate.revision) == (source.root, source.revision)
66- and "." in candidate.local_path(contract.root).name68+ and candidate.local_path(contract.root).is_file()
67 for candidate in locations69 for candidate in locations
68 ):70 ):
69 issues.append(Issue("source.navigation", "local tree source requires a same-repository, same-revision source file"))71 issues.append(Issue("source.navigation", "local tree source requires a same-repository, same-revision source file"))
@@ -0,0 +1,14 @@
1+# 2026-09-26
2+ 
3+## [03:07] fix | 修复严格来源导航对本地 Git 文件类型的误判
4+ 
5+### Summary
6+ 
7+- 本地 pinned checkout 存在时,以文件系统实际类型执行严格来源导航;checkout 缺失时跳过无法确认的类型检查。
8+- 增加无扩展名文件、带点目录和离线 checkout 的回归覆盖。
9+ 
10+### Details
11+ 
12+- `governance/contracts/checks/source_navigation.py` 不再通过文件名中的点号推断本地 Git 路径类型;同 revision 导航目标必须实际为文件。
13+- `evals/test_source_resources.py`:`PYTHONDONTWRITEBYTECODE=1 python3 -m unittest evals.test_source_resources` 实际运行 33 项,结果 `OK (skipped=1)`。
14+- `bash check.sh` 的严格 Lint 检查 7718 项且 P0/P1/P2 均为 0;662 个索引无 stale,7056 张卡的索引构建和完整验证通过。检索评测召回断言均通过,但质量门槛以 `p95=3771.6ms above 1000ms` 失败并退出 1,因此后续全库 unittest、ruff 和 shell 检查未执行。