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

+111
View File
@@ -0,0 +1,111 @@
from scripts.review_benchmark_candidates import review
from gewu.domain.validation import assertion_digest
def _inputs():
extractions = {
"extract-1": {
"extraction_id": "extract-1",
"source_revision_id": "source-1",
"blocks": [{"block_id": "block-1", "page": 1, "normalized_text": "The sky is blue."}],
}
}
item = {
"source_stage": "review",
"storage_state": "evaluated; needs-review",
"evaluation": {"status": "needs-review", "eligible_for_auto_accept": False},
"proposal": {
"proposal_id": "proposal-1",
"revision": 1,
"operations": [
{
"node": {
"claims": [
{
"claim_id": "claim-1",
"text": "The sky is blue.",
"evidence": [
{
"source_revision_id": "source-1",
"extraction_id": "extract-1",
"assertion_digest": assertion_digest("The sky is blue."),
"quote": "The sky is blue.",
"locator": {
"block_id": "block-1",
"start": 0,
"end": 16,
"page": 1,
},
}
],
}
]
}
}
],
},
}
return [item], extractions
def test_valid_evidence_does_not_clear_protected_owner_review():
items, extractions = _inputs()
result = review(items, extractions, input_sha256="b" * 64)
assert result["summary"]["evidence_bindings_valid"] == 1
assert result["items"][0]["recommendation"] == "defer"
assert result["summary"]["owner_decisions_written"] == 0
assert result["summary"]["canonical_records_written"] == 0
def test_invalid_quote_recommends_revision_without_mutating_input():
items, extractions = _inputs()
items[0]["proposal"]["operations"][0]["node"]["claims"][0]["evidence"][0]["quote"] = (
"The sky is green."
)
original = items[0]["proposal"]["operations"][0]["node"]["claims"][0]["evidence"][0]["quote"]
result = review(items, extractions, input_sha256="c" * 64)
assert result["items"][0]["recommendation"] == "revise-candidate"
assert "QUOTE_SPAN_MISMATCH" in result["items"][0]["claims"][0]["issues"]
assert (
items[0]["proposal"]["operations"][0]["node"]["claims"][0]["evidence"][0]["quote"]
== original
)
def test_missing_evidence_is_reported_for_each_claim():
items, extractions = _inputs()
items[0]["proposal"]["operations"][0]["node"]["claims"][0]["evidence"] = []
result = review(items, extractions, input_sha256="d" * 64)
assert result["items"][0]["claims"][0]["issues"] == ["CLAIM_EVIDENCE_REQUIRED"]
assert result["items"][0]["recommendation"] == "revise-candidate"
def test_assertion_digest_is_bound_to_the_exact_claim_text():
items, extractions = _inputs()
evidence = items[0]["proposal"]["operations"][0]["node"]["claims"][0]["evidence"][0]
evidence["assertion_digest"] = "0" * 64
result = review(items, extractions, input_sha256="e" * 64)
assert "ASSERTION_DIGEST_MISMATCH" in result["items"][0]["claims"][0]["issues"]
def test_supersession_edges_are_copied_to_advisory_ledger():
items, extractions = _inputs()
edge = {"relation": "corrects", "source": "old-node", "target": "new-node"}
items[0]["source_stage"] = "supersession"
items[0]["proposal"]["operations"].append({"edge": edge})
result = review(items, extractions, input_sha256="e" * 64)
row = result["items"][0]
assert row["recommendation"] == "defer"
assert row["supersession_edges"] == [edge]
assert row["supersession_edges_preserved"] == 1