From c2fe8336ebe7a95325e34b60e84faf9dbcbebbec Mon Sep 17 00:00:00 2001 From: tzhouam Date: Mon, 28 Sep 2026 21:45:39 +0800 Subject: [PATCH] Record the knowledge snapshot on every Direct review plan A review should record which knowledge it was served, so a finding can be traced to the exact rules that were active (the knowledge service now swaps snapshots at runtime). - DirectReviewPlan gains knowledge_snapshot (the activated snapshot id, "packaged", or "unverified") and knowledge_tree_sha256 (the manifest tree hash of a verified snapshot, else empty). Every document read under the plan's review_context_id comes from that same snapshot. Both fields default to empty, so existing callers are unaffected. - KnowledgeView.public_snapshot never exposes a development tree's absolute path. The old diagnostics value "unverified:/abs/path" leaked it into plans; diagnostics.knowledge_snapshot now uses the public form too. Co-Authored-By: Claude Opus 5.5 --- doc/architecture/SPEC/knowledge_view.md | 4 ++++ doc/architecture/SPEC/sdk.md | 5 +++++ src/infermatrix_copilot/direct_routing.py | 2 +- src/infermatrix_copilot/knowledge_view.py | 16 ++++++++++++++++ src/infermatrix_copilot/sdk/v1/direct.py | 2 ++ src/infermatrix_copilot/sdk/v1/models.py | 4 ++++ test/test_knowledge_view_routing.py | 19 +++++++++++++++++++ 7 files changed, 51 insertions(+), 1 deletion(-) diff --git a/doc/architecture/SPEC/knowledge_view.md b/doc/architecture/SPEC/knowledge_view.md index fbff8563..df4d98e1 100644 --- a/doc/architecture/SPEC/knowledge_view.md +++ b/doc/architecture/SPEC/knowledge_view.md @@ -31,3 +31,7 @@ stdlib + `.sdk._resources`。叶子模块。 ## 测试 `test_knowledge_view_routing.py`。 + +## 2026-09-28 可记录的快照标识 +`public_snapshot`:激活快照 ID、`packaged` 或 `unverified`(开发树的绝对路径不外泄);`tree_sha256`:已验证快照 +按清单重算的树哈希。`direct_routing` 的 `diagnostics.knowledge_snapshot` 与 SDK 计划都使用它们。 diff --git a/doc/architecture/SPEC/sdk.md b/doc/architecture/SPEC/sdk.md index 62e511b3..36f0a03d 100644 --- a/doc/architecture/SPEC/sdk.md +++ b/doc/architecture/SPEC/sdk.md @@ -128,3 +128,8 @@ Direct 1.1 / Strict 1.3 accept typed `CarriedFinding` inputs (unique IDs, source ## 2026-09-28 trace/1 导出 `sdk.v1` 导出 `TraceStore`、`TRACE_SCHEMA`、`trace_context`、`redact`(见 `trace_store.md`),RB 用同一 schema 记录 review 的模型调用、判定与结果;导入它们不加载 `config`/`contract`/`mcp_server` 等私有模块。 + +## 2026-09-28 计划携带知识快照 +`DirectReviewPlan` 新增 `knowledge_snapshot`(激活快照 ID、`packaged` 或 `unverified`,从不暴露开发树路径) +与 `knowledge_tree_sha256`(已验证快照的清单树哈希,否则为空)。同一 `review_context_id` 下的文档读取都来自这份 +快照,RB 应随每次 review 记录这两个值。 diff --git a/src/infermatrix_copilot/direct_routing.py b/src/infermatrix_copilot/direct_routing.py index b29166b3..02af7d12 100644 --- a/src/infermatrix_copilot/direct_routing.py +++ b/src/infermatrix_copilot/direct_routing.py @@ -687,7 +687,7 @@ def direct_review_plan( "if_missing": "partial_review", }, "diagnostics": { - "knowledge_snapshot": view.snapshot, + "knowledge_snapshot": view.public_snapshot, "timing_ms": { "routing": route_ms, "execution_budget": budget_ms, diff --git a/src/infermatrix_copilot/knowledge_view.py b/src/infermatrix_copilot/knowledge_view.py index 0753ecdc..bec8f462 100644 --- a/src/infermatrix_copilot/knowledge_view.py +++ b/src/infermatrix_copilot/knowledge_view.py @@ -62,6 +62,22 @@ def current(cls) -> "KnowledgeView": def verified(self) -> bool: return self.files is not None + @property + def public_snapshot(self) -> str: + """The snapshot id safe to record anywhere: the activated snapshot's id, + ``packaged``, or ``unverified`` (a development tree's path is never exposed).""" + return "unverified" if self.snapshot.startswith("unverified:") else self.snapshot + + @property + def tree_sha256(self) -> str: + """The manifest tree hash of a verified snapshot ("" otherwise).""" + if self.files is None: + return "" + tree = hashlib.sha256() + for rel in sorted(self.files): + tree.update(f"{rel}\0{self.files[rel]}\n".encode("utf-8")) + return tree.hexdigest() + def relative(self, path: str | Path) -> str: """Return the knowledge-relative id of an absolute path inside this view.""" try: diff --git a/src/infermatrix_copilot/sdk/v1/direct.py b/src/infermatrix_copilot/sdk/v1/direct.py index e565a64b..26e97902 100644 --- a/src/infermatrix_copilot/sdk/v1/direct.py +++ b/src/infermatrix_copilot/sdk/v1/direct.py @@ -378,6 +378,8 @@ def plan(self, request: DirectReviewRequest) -> DirectReviewPlan: completion_gate=completion_gate, diagnostics=dict(raw.get("diagnostics") or {}), carried_findings=request.carried_findings, + knowledge_snapshot=view.public_snapshot, + knowledge_tree_sha256=view.tree_sha256, ) self._remember_context(review_context_id, expected_head, carried, view) return plan diff --git a/src/infermatrix_copilot/sdk/v1/models.py b/src/infermatrix_copilot/sdk/v1/models.py index e54d4d7d..8306bab9 100644 --- a/src/infermatrix_copilot/sdk/v1/models.py +++ b/src/infermatrix_copilot/sdk/v1/models.py @@ -250,6 +250,10 @@ class DirectReviewPlan(_Serializable): completion_gate: dict[str, Any] diagnostics: dict[str, Any] = field(default_factory=dict) carried_findings: tuple[CarriedFinding, ...] = () + # which knowledge this plan (and every document read under its + # review_context_id) was served from: record it with the review + knowledge_snapshot: str = "" + knowledge_tree_sha256: str = "" FeedbackStatus = Literal[ "checked", "disabled", "unavailable", "not_applicable" diff --git a/test/test_knowledge_view_routing.py b/test/test_knowledge_view_routing.py index 19ff5dfb..b5af8696 100644 --- a/test/test_knowledge_view_routing.py +++ b/test/test_knowledge_view_routing.py @@ -207,6 +207,10 @@ def test_sdk_completion_pins_the_plan_snapshot(tmp_path, monkeypatch): changed_paths=(ChangedPath(path="vllm_omni/core/sched/a.py"),), )) assert plan.diagnostics["knowledge_snapshot"] == "e" * 40 + assert plan.knowledge_snapshot == "e" * 40 + manifest = json.loads((old / "MANIFEST.json").read_text()) + assert plan.knowledge_tree_sha256 == manifest["tree_sha256"] + assert plan.to_dict()["knowledge_snapshot"] == "e" * 40 tmp_link = tmp_path / "active.tmp" tmp_link.symlink_to(new) @@ -220,6 +224,21 @@ def test_sdk_completion_pins_the_plan_snapshot(tmp_path, monkeypatch): assert "provider resources changed after the review context was issued" not in decision.missing +def test_plans_from_a_development_tree_never_expose_its_path(tmp_path, monkeypatch): + from infermatrix_copilot.sdk.v1 import ChangedPath, DirectClient, DirectReviewRequest, RepositoryRef + + root = _copy_tree(tmp_path) + monkeypatch.setenv(KNOWLEDGE_ROOT_ENV, str(root)) + _load_view.cache_clear() + plan = DirectClient().plan(DirectReviewRequest( + review_id="r3", repository=RepositoryRef(alias="vllm-omni"), pr_number=3, + expected_head_sha="3" * 40, title="scheduler fix", body="", + changed_paths=(ChangedPath(path="vllm_omni/core/sched/a.py"),), + )) + assert plan.knowledge_snapshot == "unverified" and plan.knowledge_tree_sha256 == "" + assert str(tmp_path) not in json.dumps(plan.to_dict()) + + def test_sdk_document_reads_follow_the_plan_snapshot(tmp_path, monkeypatch): from infermatrix_copilot.sdk.v1 import ( ChangedPath, DirectClient, DirectReviewRequest, RepositoryRef,