feat: online verification, node removals, and knowledge browser
CI / quality (push) Canceled after 0s

Adds claim-bound online evidence records and citation verification scripts,
node removal transport/adapter, retrieval benchmark and owner-review tooling,
GraphCanvas/SourceDetail knowledge browser UI, and the ingest handoff helper.
Bumps the agents/gewu pack to the online-verification branch.
This commit is contained in:
vasily committed 2026-10-16 20:02:29 +08:00
1 parent a3156f4e08
commit 78033bb830
83 files changed
+13476 -1051

No files matched your search

+95 -2
View File
@@ -127,7 +127,7 @@ def test_authenticated_search_scope_through_http_and_mcp(tmp_path: Path) -> None
unbuilt_state = diagnostic_unbuilt.json()
assert unbuilt_state["index_state"] == "not_built"
assert unbuilt_state["index_revision"] is None
assert unbuilt_state["index_schema_version"] == 4
assert unbuilt_state["index_schema_version"] == 5
result = await http.post(
"/v1/search",
json={
@@ -138,6 +138,7 @@ def test_authenticated_search_scope_through_http_and_mcp(tmp_path: Path) -> None
},
"limit": 10,
"budget_tokens": 5000,
"include_evidence": False,
},
)
assert result.status_code == 200, result.text
@@ -145,7 +146,90 @@ def test_authenticated_search_scope_through_http_and_mcp(tmp_path: Path) -> None
assert envelope["retrieval_mode"] == "keyword"
assert envelope["index_revision"] == repository.head()
assert envelope["data"][0]["node"]["node_id"] == NODE.node_id
assert envelope["citations"][0]["quote"] == NODE.claims[0].evidence[0].quote
assert envelope["data"][0]["evidence_included"] is False
compact_claim = envelope["data"][0]["node"]["claims"][0]
assert "evidence" not in compact_claim
assert compact_claim["evidence_count"] == len(NODE.claims[0].evidence)
assert envelope["citations"] == []
full_result = await http.post(
"/v1/search",
json={
"query": "400 visitors",
"scope": {"projects": ["dome-performance"], "include_shared": False},
"limit": 10,
"budget_tokens": 5000,
"include_evidence": True,
},
)
assert full_result.status_code == 200, full_result.text
full_row = full_result.json()["data"][0]
assert full_row["evidence_included"] is True
assert full_row["node"]["claims"][0]["evidence"]
assert full_result.json()["citations"][0]["quote"] == NODE.claims[0].evidence[0].quote
browse = await http.post(
"/v1/knowledge/browse",
json={"scope": {"projects": ["dome-performance"], "include_projects": [], "include_shared": False}, "limit": 2},
)
assert browse.status_code == 200, browse.text
browse_body = browse.json()
assert browse_body["project_node_count"] == 4
assert browse_body["filtered_node_count"] == 4
assert len(browse_body["nodes"]) == 2
assert browse_body["next_cursor"]
assert [item["title"] for item in browse_body["nodes"]] == sorted(
item["title"] for item in browse_body["nodes"]
)
assert browse_body["facets"]["tags"]
browse_page_two = await http.post(
"/v1/knowledge/browse",
json={"scope": {"projects": ["dome-performance"], "include_projects": [], "include_shared": False}, "limit": 2, "cursor": browse_body["next_cursor"]},
)
assert browse_page_two.status_code == 200
assert len(browse_page_two.json()["nodes"]) == 2
assert browse_page_two.json()["next_cursor"] is None
filtered_browse = await http.post(
"/v1/knowledge/browse",
json={"scope": {"projects": ["dome-performance"], "include_projects": [], "include_shared": False}, "node_types": ["problem"]},
)
assert filtered_browse.status_code == 200
assert filtered_browse.json()["filtered_node_count"] == 1
assert filtered_browse.json()["nodes"][0]["node_type"] == "problem"
assert filtered_browse.json()["project_node_count"] == 4
graph = await http.post(
"/v1/knowledge/graph",
json={"scope": {"projects": ["dome-performance"], "include_projects": [], "include_shared": False}},
)
assert graph.status_code == 200, graph.text
assert len(graph.json()["nodes"]) == 4
assert len(graph.json()["edges"]) == 2
assert graph.json()["truncated"] is False
neighborhood = await http.post(
"/v1/knowledge/graph",
json={"scope": {"projects": ["dome-performance"], "include_projects": [], "include_shared": False}, "center_node_id": NODE.node_id},
)
assert neighborhood.status_code == 200, neighborhood.text
assert neighborhood.json()["center_node_id"] == NODE.node_id
assert NODE.node_id in {item["node_id"] for item in neighborhood.json()["nodes"]}
assert len(neighborhood.json()["nodes"]) == 3
center_only = await http.post(
"/v1/knowledge/graph",
json={"scope": {"projects": ["dome-performance"], "include_projects": [], "include_shared": False}, "center_node_id": NODE.node_id, "max_nodes": 1},
)
assert center_only.status_code == 200
assert [item["node_id"] for item in center_only.json()["nodes"]] == [NODE.node_id]
assert center_only.json()["edges"] == []
denied_neighborhood = await http.post(
"/v1/knowledge/graph",
json={"scope": {"projects": ["other-project"], "include_projects": [], "include_shared": False}, "center_node_id": NODE.node_id},
)
assert denied_neighborhood.status_code == 404
capped_graph = await http.post(
"/v1/knowledge/graph",
json={"scope": {"projects": ["dome-performance"], "include_projects": [], "include_shared": False}, "max_nodes": 1},
)
assert capped_graph.status_code == 200
assert len(capped_graph.json()["nodes"]) == 1
assert capped_graph.json()["truncated"] is True
listed_sources = await http.post(
"/v1/sources",
json={
@@ -155,6 +239,15 @@ def test_authenticated_search_scope_through_http_and_mcp(tmp_path: Path) -> None
},
)
assert listed_sources.status_code == 200, listed_sources.text
source_nodes_resp = await http.get(
f"/v1/projects/dome-performance/sources/{SOURCE_REVISION.revision_id}/nodes"
)
assert source_nodes_resp.status_code == 200, source_nodes_resp.text
source_nodes_data = source_nodes_resp.json()
assert source_nodes_data["total"] >= 1
assert source_nodes_data["nodes"][0]["relation_basis"] in ("citation", "edge", "both")
assert listed_sources.json()["filtered_count"] == 2
assert listed_sources.json()["data"][0]["source"]["revision_id"] == (
SOURCE_REVISION.revision_id
)