teleo-infrastructure/tests/test_kb_tool_propose_source.py
twentyOne2x ee48f82f06
Some checks are pending
CI / lint-and-test (push) Waiting to run
Stage source artifacts as reviewable KB proposals (#123)
2026-07-13 20:38:18 +02:00

246 lines
9.1 KiB
Python

from __future__ import annotations
import hashlib
import importlib.util
import json
import re
import stat
from pathlib import Path
from types import SimpleNamespace
import pytest
ROOT = Path(__file__).resolve().parents[1]
KB_TOOL = ROOT / "hermes-agent" / "leoclean-bin" / "kb_tool.py"
def load_module():
spec = importlib.util.spec_from_file_location("kb_tool_propose_source", KB_TOOL)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def source_bundle(module) -> dict:
payload = {
"apply_payload": {
"contract_version": 2,
"claims": [],
"sources": [],
"evidence": [],
"edges": [],
"reasoning_tools": [],
}
}
payload_sha256 = module.canonical_json_sha256(payload)
child = {
"id": "3cf4f2de-ea69-4e3b-a7ec-128b604d4f08",
"proposal_type": "approve_claim",
"status": "pending_review",
"proposed_by_handle": "leo",
"proposed_by_agent_id": None,
"channel": "source_document_compiler",
"source_ref": "normalized:source-parent:0123456789abcdef",
"rationale": "Strict normalized child of one hash-bound source packet.",
"payload": payload,
"payload_sha256": payload_sha256,
}
bundle = {
"schema": module.SOURCE_COMPILER_BUNDLE_SCHEMA,
"status": "pending_review",
"build_only": True,
"database_write_performed": False,
"canonical_apply_performed": False,
"strict_child_proposal": child,
"validated_manifest": {
"artifact_sha256": "a" * 64,
"extracted_text_sha256": "b" * 64,
"source": {
"identity": "document:sha256:" + "a" * 64,
"locator": "artifact://sha256/" + "a" * 64,
}
},
"hashes": {
"artifact_sha256": "a" * 64,
"extracted_text_sha256": "b" * 64,
"manifest_canonical_sha256": "c" * 64,
"strict_child_payload_sha256": payload_sha256,
},
}
bundle["hashes"]["bundle_content_sha256"] = module.canonical_json_sha256(bundle)
return bundle
def status_row(proposal_count: int) -> dict:
return {
"artifact": "teleo_kb_status",
"backend": "teleo|postgres",
"high_signal_rows": {
"claims": 1837,
"sources": 4145,
"claim_edges": 4916,
"claim_evidence": 4670,
"kb_proposals": proposal_count,
},
"proposal_status_counts": {"pending_review": 14},
}
def test_propose_source_stages_only_pending_review_and_writes_private_receipt(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
module = load_module()
bundle = source_bundle(module)
child = bundle["strict_child_proposal"]
compiler = tmp_path / "compile_kb_source_packet.py"
compiler.write_text("# compiler fixture\n", encoding="utf-8")
monkeypatch.setattr(module, "SOURCE_COMPILER_PATH", compiler)
monkeypatch.setattr(module, "compile_source_bundle", lambda _args: bundle)
calls: list[str] = []
status_reads = iter((status_row(26), status_row(27)))
def fake_psql_json(_args, sql):
calls.append(sql)
if "insert into kb_stage.kb_proposals" not in sql:
return [next(status_reads)]
return [
{
"created": True,
"proposal_id": child["id"],
"proposal_type": child["proposal_type"],
"status": "pending_review",
"source_ref": child["source_ref"],
"payload": child["payload"],
"payload_sha256": child["payload_sha256"],
"created_at": "2026-07-13T18:00:00+00:00",
"reviewed_at": None,
"applied_at": None,
}
]
monkeypatch.setattr(module, "psql_json", fake_psql_json)
receipt_path = tmp_path / "receipts" / "source.json"
args = SimpleNamespace(
local=True,
artifact=tmp_path / "source.pdf",
text=tmp_path / "source.txt",
manifest=tmp_path / "source-manifest.json",
receipt=receipt_path,
)
receipt = module.propose_source(args)
assert receipt["status"] == "pending_review"
assert receipt["created"] is True
assert receipt["proposal"]["id"] == child["id"]
assert receipt["database"]["canonical_counts_unchanged"] is True
assert receipt["database"]["proposal_count_delta"] == 1
assert receipt["database"]["proposal_count_matches_command"] is True
assert receipt["write_contract"] == {
"target_table": "kb_stage.kb_proposals",
"database_write_performed_by_command": True,
"canonical_apply_performed": False,
"production_db_apply_ran": False,
"public_table_write_path_present": False,
}
assert json.loads(receipt_path.read_text(encoding="utf-8")) == receipt
assert stat.S_IMODE(receipt_path.stat().st_mode) == 0o600
stage_sql = next(sql for sql in calls if "insert into kb_stage.kb_proposals" in sql)
assert "on conflict (id) do nothing" in stage_sql
assert "marker_count <> 1 or exact_count <> 1" in stage_sql
assert not re.search(r"\b(?:insert|update|delete)\s+(?:into\s+|from\s+)?public\.", stage_sql, re.I)
assert len(calls) == 3
def test_source_bundle_rejects_tampered_child_payload() -> None:
module = load_module()
bundle = source_bundle(module)
bundle["strict_child_proposal"]["payload"]["apply_payload"]["claims"].append({"id": "tampered"})
with pytest.raises(SystemExit, match="payload hash does not match"):
module._validated_source_bundle(bundle)
def test_deployed_bridge_accepts_real_source_compiler_bundle(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
module = load_module()
artifact_bytes = b"%PDF-1.7\nbridge compatibility fixture\n%%EOF\n"
extracted_text = (
"Bridge compatibility source.\n\n"
"Prepared source inputs may become a pending review proposal.\n\n"
"Canonical rows still require review and guarded apply."
)
artifact = tmp_path / "source.pdf"
text = tmp_path / "source.txt"
manifest_path = tmp_path / "source-manifest.json"
artifact.write_bytes(artifact_bytes)
text.write_text(extracted_text, encoding="utf-8")
artifact_sha256 = hashlib.sha256(artifact_bytes).hexdigest()
text_sha256 = hashlib.sha256(extracted_text.encode()).hexdigest()
manifest = {
"schema": "livingip.kbSourceExtractionManifest.v1",
"artifact_sha256": artifact_sha256,
"extracted_text_sha256": text_sha256,
"extractor": {"name": "bridge-test", "version": "1"},
"source": {
"identity": f"document:sha256:{artifact_sha256}",
"source_key": "bridge_compatibility_source",
"source_type": "article",
"title": "Bridge compatibility source",
"locator": f"artifact://sha256/{artifact_sha256}",
},
"claims": [
{
"claim_key": "prepared_input_staging",
"type": "structural",
"text": "Prepared inputs can be staged for review without becoming canonical.",
"quote": "Prepared source inputs may become a pending review proposal.",
"confidence": 0.99,
"tags": ["source-intake"],
"evidence": [
{
"quote": "Canonical rows still require review and guarded apply.",
"role": "grounds",
}
],
}
],
}
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
monkeypatch.setattr(module, "SOURCE_COMPILER_PATH", ROOT / "scripts" / "compile_kb_source_packet.py")
bundle = module.compile_source_bundle(SimpleNamespace(artifact=artifact, text=text, manifest=manifest_path))
child, validated_manifest = module._validated_source_bundle(bundle)
assert child["status"] == "pending_review"
assert child["proposal_type"] == "approve_claim"
assert validated_manifest["artifact_sha256"] == artifact_sha256
def test_propose_source_refuses_nonlocal_database_route(tmp_path: Path) -> None:
module = load_module()
args = SimpleNamespace(
local=False,
artifact=tmp_path / "source.pdf",
text=tmp_path / "source.txt",
manifest=tmp_path / "source-manifest.json",
receipt=tmp_path / "receipt.json",
)
with pytest.raises(SystemExit, match="VPS-local only"):
module.propose_source(args)
def test_source_stage_sql_allows_idempotent_readback_after_review_state_advances() -> None:
module = load_module()
child, _manifest = module._validated_source_bundle(source_bundle(module))
sql = module.build_source_stage_sql(child)
assert "on conflict (id) do nothing" in sql
exact_clause = sql.split("select count(*) into exact_count", 1)[1].split("if marker_count", 1)[0]
assert "status = 'pending_review'" not in exact_clause
assert "reviewed_at is null" not in exact_clause
assert "applied_at is null" not in exact_clause