- Normalize rich proposals into atomic reviewed canonical graph bundles with exact row verification. - Harden reviewer/apply roles, upgrade ACLs, and prove generic plus Helmer lifecycles in isolated PostgreSQL. - Publish repo-native VPS/GCP/Cory skills and live-readonly GCP inventory without changing the live VPS runtime. `.agents/skills/crabbox/SKILL.md` `.agents/skills/teleo-gcp-parity-ops/SKILL.md` `.agents/skills/teleo-kb-db-change-workflow/SKILL.md` `.agents/skills/teleo-leo-onboarding/SKILL.md` `.agents/skills/teleo-vps-runtime-ops/SKILL.md` `.agents/skills/working-leo-cory-outcomes/SKILL.md` `docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.json` `docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.md` `docs/reports/leo-working-state-20260709/current-truth-index.md` `docs/reports/leo-working-state-20260709/gcp-cloud-sql-t3-live-readonly-current.json` `docs/reports/leo-working-state-20260709/gcp-cloud-sql-t3-live-readonly-current.md` `docs/reports/leo-working-state-20260709/gcp-cloudsql-studio-parity-gate-current.json` `docs/reports/leo-working-state-20260709/gcp-cloudsql-studio-parity-gate-current.md` `docs/reports/leo-working-state-20260709/gcp-parallel-delegation-current.json` `docs/reports/leo-working-state-20260709/helmer-approve-claim-clone-canary-current.json` `docs/reports/leo-working-state-20260709/helmer-approve-claim-normalization-current.json` `docs/reports/leo-working-state-20260709/skill-pack-manifest.json` `docs/reports/leo-working-state-20260709/skill-pack-readme.md` `docs/reports/leo-working-state-20260709/working-leo-current-state-20260709.md` `docs/reports/leo-working-state-20260709/working-leo-definition-20260709.md` `docs/reports/leo-working-state-20260709/working-leo-execution-plan-current.md` `scripts/apply_proposal.py` `scripts/apply_worker.py` `scripts/approve_proposal.py` `scripts/kb_apply_prereqs.sql` `scripts/kb_proposal_normalize.py` `scripts/probe_gcp_db_parity.py` `scripts/run_approve_claim_clone_canary.py` `scripts/run_approve_claim_isolated_container_canary.sh` `tests/test_apply_proposal.py` `tests/test_apply_worker.py` `tests/test_approve_proposal.py` `tests/test_kb_apply_prereqs.py` `tests/test_kb_proposal_normalize.py` `tests/test_kb_proposal_routes.py` `tests/test_probe_gcp_db_parity.py` `tests/test_repo_skill_pack.py`
319 lines
12 KiB
Python
319 lines
12 KiB
Python
"""Tests for scripts/kb_proposal_normalize.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
|
|
import kb_proposal_normalize as norm # noqa: E402
|
|
|
|
|
|
def test_rich_claim_split_stays_atomic_when_one_fragment_is_blocked():
|
|
preview = norm.normalize_proposal(
|
|
{
|
|
"id": "p-freeform",
|
|
"proposal_type": "attach_evidence",
|
|
"status": "approved",
|
|
"payload": {
|
|
"old_claim": {"id": "d0000000-0000-0000-0000-000000000005"},
|
|
"claim_candidates": [
|
|
{
|
|
"claim_key": "new_claim_key",
|
|
"headline": "New claim headline",
|
|
"edges": [
|
|
{
|
|
"edge_type": "supersedes_component_of",
|
|
"target_claim_id": "d0000000-0000-0000-0000-000000000005",
|
|
}
|
|
],
|
|
"evidence": [{"source_key": "human_review"}],
|
|
}
|
|
],
|
|
"source_candidates": [{"source_key": "human_review"}],
|
|
},
|
|
}
|
|
)
|
|
|
|
assert preview["normalization_state"] == "blocked_rich_normalization"
|
|
assert preview["strict_child_count"] == 0
|
|
assert preview["strict_child_proposals"] == []
|
|
assert preview["blocked_count"] == 1
|
|
assert "unsupported edge_type" in preview["blocked_fragments"][0]["reason"]
|
|
assert "Resolve every blocked rich fragment" in preview["next_normalization_action"]
|
|
|
|
|
|
def test_exact_edge_fragment_emits_strict_add_edge_child():
|
|
preview = norm.normalize_proposal(
|
|
{
|
|
"id": "p-edge",
|
|
"proposal_type": "attach_evidence",
|
|
"status": "approved",
|
|
"payload": {
|
|
"proposed_supersession_edges": [
|
|
{
|
|
"from_claim": "11111111-1111-1111-1111-111111111111",
|
|
"to_claim_id": "22222222-2222-2222-2222-222222222222",
|
|
"edge_type": "supports",
|
|
"weight": 0.7,
|
|
}
|
|
]
|
|
},
|
|
}
|
|
)
|
|
|
|
assert preview["normalization_state"] == "strict_children_ready"
|
|
assert preview["blocked_count"] == 0
|
|
assert preview["strict_child_count"] == 1
|
|
child = preview["strict_child_proposals"][0]
|
|
assert child["proposal_type"] == "add_edge"
|
|
assert child["payload"]["apply_payload"] == {
|
|
"from_claim": "11111111-1111-1111-1111-111111111111",
|
|
"to_claim": "22222222-2222-2222-2222-222222222222",
|
|
"edge_type": "supports",
|
|
"weight": 0.7,
|
|
}
|
|
|
|
|
|
def test_exact_evidence_fragments_emit_one_attach_evidence_child():
|
|
preview = norm.normalize_proposal(
|
|
{
|
|
"id": "p-evidence",
|
|
"proposal_type": "attach_evidence",
|
|
"status": "approved",
|
|
"payload": {
|
|
"evidence": [
|
|
{
|
|
"claim_id": "33333333-3333-3333-3333-333333333333",
|
|
"source_id": "44444444-4444-4444-4444-444444444444",
|
|
"role": "grounds",
|
|
"weight": 0.9,
|
|
}
|
|
]
|
|
},
|
|
}
|
|
)
|
|
|
|
assert preview["normalization_state"] == "strict_children_ready"
|
|
assert preview["strict_child_count"] == 1
|
|
child = preview["strict_child_proposals"][0]
|
|
assert child["proposal_type"] == "attach_evidence"
|
|
assert child["payload"]["apply_payload"]["evidence"] == [
|
|
{
|
|
"claim_id": "33333333-3333-3333-3333-333333333333",
|
|
"source_id": "44444444-4444-4444-4444-444444444444",
|
|
"role": "grounds",
|
|
"weight": 0.9,
|
|
}
|
|
]
|
|
|
|
|
|
def test_existing_apply_payload_is_already_strict():
|
|
preview = norm.normalize_proposal(
|
|
{
|
|
"id": "p-strict",
|
|
"proposal_type": "add_edge",
|
|
"status": "approved",
|
|
"payload": {
|
|
"apply_payload": {
|
|
"from_claim": "11111111-1111-1111-1111-111111111111",
|
|
"to_claim": "22222222-2222-2222-2222-222222222222",
|
|
"edge_type": "supports",
|
|
}
|
|
},
|
|
}
|
|
)
|
|
|
|
assert preview["normalization_state"] == "already_strict_apply_payload"
|
|
assert preview["strict_child_count"] == 0
|
|
assert preview["blocked_count"] == 0
|
|
|
|
|
|
def test_rich_claim_graph_normalizes_to_one_atomic_approve_claim_bundle():
|
|
preview = norm.normalize_proposal(
|
|
{
|
|
"id": "77777777-7777-4777-8777-777777777777",
|
|
"proposal_type": "attach_evidence",
|
|
"status": "approved",
|
|
"proposed_by_agent_id": "11111111-1111-4111-8111-111111111111",
|
|
"source_ref": "telegram:test:approved",
|
|
"payload": {
|
|
"claim_candidates": [
|
|
{
|
|
"claim_key": "canonical_test_claim",
|
|
"headline": "A reviewed claim becomes canonical after approval.",
|
|
"type": "structural",
|
|
"confidence": 0.8,
|
|
"tags": ["working-leo"],
|
|
"evidence": [{"source_key": "canonical_test_source", "role": "grounds"}],
|
|
}
|
|
],
|
|
"proposed_claims": [
|
|
{
|
|
"claim_key": "second_canonical_test_claim",
|
|
"text": "A proposed_claims item is normalized too.",
|
|
"type": "normative",
|
|
}
|
|
],
|
|
"source_candidates": [
|
|
{
|
|
"source_key": "canonical_test_source",
|
|
"source_type": "paper",
|
|
"url": "https://example.test/canonical",
|
|
"key_excerpt": "Reviewed source excerpt.",
|
|
}
|
|
],
|
|
},
|
|
}
|
|
)
|
|
|
|
assert preview["normalization_state"] == "strict_children_ready"
|
|
assert preview["blocked_count"] == 0
|
|
assert preview["strict_child_count"] == 1
|
|
child = preview["strict_child_proposals"][0]
|
|
assert child["proposal_type"] == "approve_claim"
|
|
bundle = child["payload"]["apply_payload"]
|
|
assert bundle["agent_id"] == "11111111-1111-4111-8111-111111111111"
|
|
assert len(bundle["claims"]) == 2
|
|
assert len(bundle["sources"]) == 3 # reviewed source plus two proposal-body sources
|
|
assert len(bundle["evidence"]) == 3
|
|
assert bundle["edges"] == []
|
|
accounting = bundle["normalization_manifest"]["candidate_accounting"]
|
|
assert accounting["input_collections"] == {
|
|
"claim_candidates": 1,
|
|
"proposed_claims": 1,
|
|
"source_candidates": 1,
|
|
}
|
|
assert accounting["claim_candidates"]["input_count"] == 2
|
|
assert accounting["claim_candidates"]["planned_row_count"] == 2
|
|
assert accounting["source_candidates"]["input_count"] == 1
|
|
assert accounting["source_candidates"]["planned_row_count"] == 1
|
|
assert accounting["generated_claim_body_sources"]["planned_row_count"] == 2
|
|
assert accounting["total_planned_source_rows"] == 3
|
|
|
|
|
|
def test_rich_normalization_blocks_malformed_or_duplicate_candidates():
|
|
proposal = {
|
|
"id": "77777777-7777-4777-8777-777777777777",
|
|
"proposal_type": "attach_evidence",
|
|
"status": "approved",
|
|
"payload": {
|
|
"claim_candidates": [
|
|
{"claim_key": "duplicate", "text": "first"},
|
|
{"claim_key": "duplicate", "text": "second"},
|
|
{"text": "missing key"},
|
|
"not-an-object",
|
|
],
|
|
"proposed_claims": [
|
|
{"claim_key": "duplicate", "text": "duplicate across claim collections"},
|
|
{"claim_key": " ", "text": "blank key"},
|
|
42,
|
|
],
|
|
"source_candidates": [
|
|
{"source_key": "source", "source_type": "paper"},
|
|
{"source_key": "source", "source_type": "paper"},
|
|
],
|
|
},
|
|
}
|
|
preview = norm.normalize_proposal(proposal)
|
|
assert preview["normalization_state"] == "blocked_lossy_rich_normalization"
|
|
assert preview["strict_child_count"] == 0
|
|
assert preview["strict_child_proposals"] == []
|
|
reasons = " ".join(item["reason"] for item in preview["blocked_fragments"])
|
|
assert "duplicate" in reasons
|
|
assert "missing" in reasons
|
|
assert "object" in reasons
|
|
|
|
|
|
def test_rich_normalization_retains_type_mapping_manifest_for_review():
|
|
preview = norm.normalize_proposal(
|
|
{
|
|
"id": "77777777-7777-4777-8777-777777777778",
|
|
"proposal_type": "attach_evidence",
|
|
"status": "approved",
|
|
"payload": {
|
|
"claim_candidates": [
|
|
{"claim_key": "governance_claim", "text": "Governance process.", "type": "governance"},
|
|
{"claim_key": "typed_claim", "text": "Typed detail.", "type": "normative/detail"},
|
|
],
|
|
"source_candidates": [
|
|
{"source_key": "book_source", "source_type": "book", "key_excerpt": "Excerpt"},
|
|
{"source_key": "web_source", "url": "https://example.test/source"},
|
|
],
|
|
},
|
|
}
|
|
)
|
|
child = preview["strict_child_proposals"][0]
|
|
manifest = child["payload"]["apply_payload"]["normalization_manifest"]
|
|
assert {
|
|
(item["claim_key"], item["from"], item["to"])
|
|
for item in manifest["claim_type_mappings"]
|
|
} == {
|
|
("governance_claim", "governance", "meta"),
|
|
("typed_claim", "normative/detail", "normative"),
|
|
}
|
|
assert {
|
|
(item["source_key"], item["from"], item["to"])
|
|
for item in manifest["source_type_mappings"]
|
|
} == {
|
|
("book_source", "book", "other"),
|
|
("web_source", "url", "article"),
|
|
("governance_claim__proposal_body", "proposal_body", "other"),
|
|
("typed_claim__proposal_body", "proposal_body", "other"),
|
|
}
|
|
|
|
|
|
def test_rich_normalization_rejects_non_list_candidate_collection():
|
|
preview = norm.normalize_proposal(
|
|
{
|
|
"id": "77777777-7777-4777-8777-777777777779",
|
|
"proposal_type": "attach_evidence",
|
|
"payload": {"claim_candidates": {"claim_key": "not-in-a-list"}},
|
|
}
|
|
)
|
|
|
|
assert preview["normalization_state"] == "blocked_lossy_rich_normalization"
|
|
assert preview["strict_child_proposals"] == []
|
|
assert preview["blocked_fragments"][0]["location"] == "payload.claim_candidates"
|
|
assert "list of objects" in preview["blocked_fragments"][0]["reason"]
|
|
|
|
|
|
def test_rich_normalization_fails_closed_when_planner_skips_candidate(monkeypatch):
|
|
proposal = {
|
|
"id": "77777777-7777-4777-8777-777777777780",
|
|
"proposal_type": "attach_evidence",
|
|
"payload": {
|
|
"claim_candidates": [{"claim_key": "must_be_planned", "text": "Keep me."}],
|
|
"source_candidates": [{"source_key": "must_be_planned_too", "source_type": "paper"}],
|
|
},
|
|
}
|
|
lossy_plan = norm.rich_plan.build_creation_plan(proposal)
|
|
lossy_plan["planned_rows"]["claims"] = []
|
|
monkeypatch.setattr(norm.rich_plan, "build_creation_plan", lambda *_args, **_kwargs: lossy_plan)
|
|
|
|
preview = norm.normalize_proposal(proposal)
|
|
|
|
assert preview["normalization_state"] == "blocked_lossy_rich_normalization"
|
|
assert preview["strict_child_proposals"] == []
|
|
assert preview["strict_child_count"] == 0
|
|
assert any("claim rows" in item["reason"] for item in preview["blocked_fragments"])
|
|
|
|
|
|
def test_source_candidates_without_claims_are_not_silently_dropped():
|
|
preview = norm.normalize_proposal(
|
|
{
|
|
"id": "77777777-7777-4777-8777-777777777781",
|
|
"proposal_type": "attach_evidence",
|
|
"payload": {
|
|
"source_candidates": [
|
|
{"source_key": "orphan_source", "source_type": "paper", "key_excerpt": "Keep me."}
|
|
]
|
|
},
|
|
}
|
|
)
|
|
|
|
assert preview["normalization_state"] == "blocked_lossy_rich_normalization"
|
|
assert preview["strict_child_proposals"] == []
|
|
assert "without at least one claim candidate" in preview["blocked_fragments"][0]["reason"]
|