From 40918a7fb12ec74a8bc54c85912fd893c9670b12 Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Wed, 21 Oct 2026 15:39:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AF=E7=96=91=E5=AE=9E=E4=BD=93=E6=89=BE?= =?UTF-8?q?=E3=80=8C=E6=9C=80=E6=8E=A5=E8=BF=91=E7=9A=84=E5=90=8D=E5=AD=97?= =?UTF-8?q?=E3=80=8D=E6=97=B6=E9=87=8D=E5=BB=BA=E7=9A=84=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=EF=BC=88=5Fscope=5Fcatalog=EF=BC=89=E4=B8=8D=E6=94=B6=E5=88=86?= =?UTF-8?q?=E6=9E=90=E7=BB=93=E8=AE=BA=EF=BC=9A=E5=80=99=E9=80=89=E5=8F=AA?= =?UTF-8?q?=E7=9C=8B=E8=A1=A8=E5=92=8C=E5=AD=97=E6=AE=B5=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E4=B8=8D=E5=88=B0=E7=BB=93=E8=AE=BA=E7=9A=84=E5=86=85=E5=AE=B9?= =?UTF-8?q?=EF=BC=8C=E4=BB=A5=E5=89=8D=E6=AF=8F=E7=82=B9=E5=BC=80=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E5=8F=AF=E7=96=91=E7=9A=84=E5=90=8D=E5=AD=97=E9=83=BD?= =?UTF-8?q?=E6=8A=8A=E8=BF=99=E6=AC=A1=E8=BF=90=E8=A1=8C=E7=9A=84=E5=88=86?= =?UTF-8?q?=E6=9E=90=E7=BB=93=E8=AE=BA=E8=AF=BB=E7=9B=98=E3=80=81=E5=A4=8D?= =?UTF-8?q?=E9=AA=8C=E4=B8=80=E9=81=8D=E5=93=88=E5=B8=8C=EF=BC=88build=5Fc?= =?UTF-8?q?atalog=20=E7=BB=99=20A=20=E6=9D=A1=E7=9B=AE=E8=A3=85=E6=95=B4?= =?UTF-8?q?=E4=BB=BD=E7=BB=93=E8=AE=BA=EF=BC=89=E3=80=82=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E3=80=81=E8=A1=A8=E5=92=8C=E5=AD=97=E6=AE=B5=E7=9A=84=E7=BC=96?= =?UTF-8?q?=E5=8F=B7=E4=B8=8D=E5=8F=97=E5=BD=B1=E5=93=8D=E3=80=82=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=EF=BC=9A=E5=8F=B0=E8=B4=A6=E9=87=8C=E6=9C=89=E5=88=86?= =?UTF-8?q?=E6=9E=90=E7=BB=93=E8=AE=BA=E6=97=B6=E9=87=8D=E5=BB=BA=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E4=B8=8D=E8=AF=BB=E4=BB=BB=E4=BD=95=E5=B7=A5=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=8C=89=E6=8A=A5=E5=91=8A=E8=8A=82=E7=82=B9=E5=92=8C?= =?UTF-8?q?=E9=80=80=E5=9B=9E=E4=B8=8D=E5=88=86=E8=8C=83=E5=9B=B4=E4=B8=A4?= =?UTF-8?q?=E6=9D=A1=E8=B7=AF=E9=83=BD=E6=98=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/evidence.py | 8 ++++++-- backend/tests/test_analysis_evidence_api.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/backend/app/api/evidence.py b/backend/app/api/evidence.py index 9b768b15..741dab46 100644 --- a/backend/app/api/evidence.py +++ b/backend/app/api/evidence.py @@ -4048,18 +4048,22 @@ def _scope_catalog(sealed: _Sealed, report_node: str) -> dict[str, Any]: doc.catalog 只留了正文用到的实体,给「是不是想写…」找候选不够;运行状态(checkpoint)不在封存 范围里,也不能用。图读不出来、找不到报告节点时退回全部封存台账。 + + 台账里的分析结论不进:候选只看表和字段,用不到结论的内容,进了目录就要把每份结论读盘、复验一遍哈希(build_catalog + 给 A 条目装整份结论)。查询、表和字段的编号不受影响(分析结论另编 A)。 """ from app.engine.nodes.report import report_catalog from app.engine.schema import GraphSpec + entries = [e for e in sealed.entries if e.get("kind") != LEDGER_KIND] try: spec = GraphSpec.model_validate(sealed.graph) node = spec.node_map().get(report_node) except Exception: # noqa: BLE001 - 老运行的图可能已经不合现在的格式,退回不分范围 node = None if node is None: - return build_catalog(nodes={}, ledger=sealed.entries) - return report_catalog({"evidence": sealed.entries, "nodes": {}, "input": {}}, spec, node) # type: ignore[arg-type] + return build_catalog(nodes={}, ledger=entries) + return report_catalog({"evidence": entries, "nodes": {}, "input": {}}, spec, node) # type: ignore[arg-type] def _suspicious_step(seg: dict[str, Any], cite: dict[str, Any], sealed: _Sealed, report_node: str) -> dict[str, Any]: diff --git a/backend/tests/test_analysis_evidence_api.py b/backend/tests/test_analysis_evidence_api.py index cb1b891d..02bff472 100644 --- a/backend/tests/test_analysis_evidence_api.py +++ b/backend/tests/test_analysis_evidence_api.py @@ -287,6 +287,25 @@ async def test_a_tampered_result_is_said_and_not_shown(client): assert seal["hash_ok"] is False and seal["sealed"] is False and body["seal"]["covered"] is False +def test_suspicious_name_hints_do_not_read_the_analysis_results(monkeypatch): + """可疑实体找「最接近的名字」时按封存的台账重建目录(_scope_catalog):分析结论不进,不读盘、不复验哈希(候选只看表和 + 字段,用不到结论的内容)。以前每点开一个可疑的名字,就把这次运行的分析结论全读一遍。""" + from app.api import evidence as api + from app.engine import evidence as engine_evidence + + read: list[str] = [] + monkeypatch.setattr(engine_evidence, "load_artifact", lambda artifact: read.append(artifact)) + q, a = "1" * 64, "2" * 64 + entries = [_query_entry(SNAPSHOT, q), {"kind": "analysis", "node_id": "an", "exec": 1, "artifact": a, + "op": "decompose", "mode": "additive", "item": "drop", "inputs": [q]}] + graph = {**GRAPH, "edges": [{"source": "fetch", "target": "an"}, {"source": "an", "target": "write"}]} + sealed = api._Sealed(run_id="r", graph=graph, seal={"sealed": True, "ok": True}, events=[], entries=entries) + for report in ("write", "nope"): + catalog = api._scope_catalog(sealed, report) + assert "Q1" in catalog and not [e for e in catalog.values() if e.get("kind") == "analysis"] + assert read == [] + + async def test_agent_analyses_are_marked_as_such(client): made = await sealed_run(TEXT, item=None, call_id="call-9") body = (await client.get(f"/api/runs/{made['run']}/evidence/segments/{seg_id(made['doc'], 'a:A1')}")).json()