#!/usr/bin/env python3 """Apply an approved kb_stage proposal into canonical public.* rows. Stage 2 of the KB apply pipeline: approve -> APPLY -> render -> surface. Governance boundary ------------------- This tool connects to Postgres as the narrow ``kb_apply`` login role, never as superuser. ``kb_apply`` can insert reviewed claim/source/reasoning-tool rows and write the strategy/evidence/edge tables required by supported contracts. It has SELECT-only access to ``kb_stage.kb_proposals``; payload-bound SECURITY DEFINER functions are the only way it can lock and finish an approved ledger row. It holds no DELETE or DDL and no write access to unrelated tables (agents, budgets, personas, ...). This enforces "agents propose, but do not self-apply" at the database boundary rather than by convention. The tool is an operator, invoked by a human/reviewer after approval; it is deliberately not part of the agent runtime. Flow ---- 1. Read the proposal (must be ``status='approved'``). 2. Dispatch by ``proposal_type`` to build the canonical write from a strict ``apply_payload`` contract carried on the proposal payload. 3. Emit a single transaction: canonical writes, then a DO block that flips the ledger to ``applied`` asserting rowcount=1 (the concurrent double-apply guard), stamps ``applied_by_agent_id`` as a real FK, and verifies invariants (RAISE on violation -> rollback), COMMIT. Prerequisites (one-time, superuser): scripts/kb_apply_prereqs.sql bootstraps the ``kb-apply`` service-agent row, grants ``kb_apply`` SELECT on public.agents, and ensures the one-active-strategy unique index. Run it before the first apply. ``--dry-run`` prints the exact SQL and does not connect for writes. Contract (v2) ------------- Freeform Leo eval packets are NOT applied directly. Each proposal must carry a strict ``apply_payload`` object; a separate normalizer produces it. Shapes: revise_strategy: {"apply_payload": { "agent_id": "", "strategy": {"diagnosis": str, "guiding_policy": str, "proximate_objectives": [ ... ]}, "strategy_nodes": [{"node_type": "diagnosis|policy|objective", "title": str, "body": str, "rank": int}, ...]}} add_edge: {"apply_payload": {"from_claim": "", "to_claim": "", "edge_type": "", "weight": <0..1|null>}} attach_evidence: {"apply_payload": {"evidence": [{"claim_id": "", "source_id": "", "role": "", "weight": <0..1|null>}, ...]}} approve_claim: {"apply_payload": { "agent_id": "", "claims": [{"id": "", "type": "", "text": str, "status": "open", "confidence": <0..1|null>, "tags": [str, ...], "created_by": ""}, ...], "sources": [{"id": "", "source_type": "", "url": str|null, "storage_path": str|null, "excerpt": str|null, "hash": str, "created_by": ""}, ...], "evidence": [{"claim_id": "", "source_id": "", "role": "", "weight": <0..1|null>, "created_by": ""}, ...], "edges": [{"from_claim": "", "to_claim": "", "edge_type": "", "weight": <0..1|null>, "created_by": ""}, ...], "reasoning_tools": [{"id": "", "agent_id": "", "name": str, "description": str, "category": str|null}, ...]}} ``approve_claim`` is the only source-creation contract. It is an atomic, insert-only canonical bundle: existing IDs must match the reviewed payload exactly, source hashes cannot alias another ID, and every row is verified before the approved proposal ledger can flip to ``applied``. """ from __future__ import annotations import argparse import json import subprocess import sys import uuid from pathlib import Path from typing import Any DEFAULT_SECRETS_FILE = "/home/teleo/.hermes/profiles/leoclean/secrets/kb-apply.env" DEFAULT_CONTAINER = "teleo-pg" DEFAULT_DB = "teleo" DEFAULT_HOST = "127.0.0.1" DEFAULT_ROLE = "kb_apply" # Handle of the service-agent row that performs canonical writes. Bootstrapped # once by superuser (scripts/kb_apply_prereqs.sql); kb_apply holds SELECT-only on # public.agents so it can resolve this into applied_by_agent_id, never INSERT. SERVICE_AGENT_HANDLE = "kb-apply" APPLYABLE_TYPES = ("revise_strategy", "add_edge", "attach_evidence", "approve_claim") CLAIM_TYPES = {"empirical", "structural", "normative", "meta", "concept"} SOURCE_TYPES = {"paper", "article", "transcript", "observation", "dataset", "post", "dm", "other"} EVIDENCE_ROLES = {"grounds", "illustrates", "contradicts"} EDGE_TYPES = { "supports", "challenges", "requires", "relates", "contradicts", "supersedes", "derives_from", "cites", "causes", "constrains", "accelerates", } MAX_BUNDLE_ROWS = { "claims": 100, "sources": 500, "evidence": 1000, "edges": 1000, "reasoning_tools": 50, } # --------------------------------------------------------------------------- # # SQL helpers # # --------------------------------------------------------------------------- # def sql_literal(value: Any) -> str: """Render a Python value as an explicit PostgreSQL escape-string literal.""" if value is None: return "null" if isinstance(value, bool): return "true" if value else "false" if isinstance(value, (int, float)): return repr(value) escaped = str(value).replace("\\", "\\\\").replace("'", "''") return "E'" + escaped + "'" def _jsonb(value: Any) -> str: return sql_literal(json.dumps(value, sort_keys=True)) + "::jsonb" def _text_array(values: list[str]) -> str: if not values: return "'{}'::text[]" return "array[" + ", ".join(sql_literal(value) for value in values) + "]::text[]" def _uuid(value: Any, path: str, *, nullable: bool = False) -> str | None: if value is None and nullable: return None try: return str(uuid.UUID(str(value))) except (TypeError, ValueError, AttributeError) as exc: raise ValueError(f"{path} must be a UUID{' or null' if nullable else ''}") from exc def _weight(value: Any, path: str) -> Any: if value is None: return None if isinstance(value, bool) or not isinstance(value, (int, float)): raise ValueError(f"{path} must be a number from 0 to 1 or null") if not 0 <= value <= 1: raise ValueError(f"{path} must be between 0 and 1") return value def _rows(payload: dict[str, Any], key: str, *, required: bool = False) -> list[dict[str, Any]]: value = payload.get(key) if value is None: value = [] if not isinstance(value, list) or any(not isinstance(row, dict) for row in value): raise ValueError(f"approve_claim apply_payload.{key} must be a list of objects") if required and not value: raise ValueError(f"approve_claim apply_payload requires non-empty '{key}'") if len(value) > MAX_BUNDLE_ROWS[key]: raise ValueError(f"approve_claim apply_payload.{key} exceeds {MAX_BUNDLE_ROWS[key]} rows") return value def _dedupe(values: list[Any], path: str) -> None: seen = set() for value in values: marker = json.dumps(value, sort_keys=True) if marker in seen: raise ValueError(f"{path} contains a duplicate: {value!r}") seen.add(marker) def _reject_unknown(mapping: dict[str, Any], allowed: set[str], path: str) -> None: unknown = sorted(set(mapping) - allowed) if unknown: raise ValueError(f"{path} contains unknown fields: {unknown}") def _validate_approval_meta( approval: dict[str, Any], proposal_type: str, apply_payload: dict[str, Any] ) -> None: if not isinstance(approval, dict): raise ValueError("approved proposal metadata is required") if approval.get("proposal_type") != proposal_type: raise ValueError("approved proposal_type does not match the apply builder") payload = approval.get("payload") if not isinstance(payload, dict) or payload.get("apply_payload") != apply_payload: raise ValueError("approved proposal payload does not match the strict apply payload") if not str(approval.get("reviewed_by_handle") or "").strip(): raise ValueError("approved proposal requires reviewed_by_handle") if not str(approval.get("reviewed_at") or "").strip(): raise ValueError("approved proposal requires reviewed_at") if not str(approval.get("review_note") or "").strip(): raise ValueError("approved proposal requires review_note") _uuid( approval.get("reviewed_by_agent_id"), "approved proposal reviewed_by_agent_id", nullable=True, ) def _approval_guard_sql(proposal_id: str, approval: dict[str, Any]) -> str: return f"""select kb_stage.assert_approved_proposal( {sql_literal(proposal_id)}::uuid, {sql_literal(approval['proposal_type'])}, {_jsonb(approval['payload'])}, {sql_literal(approval['reviewed_by_handle'])}, {sql_literal(approval.get('reviewed_by_agent_id'))}::uuid, {sql_literal(approval['reviewed_at'])}::timestamptz, {sql_literal(approval['review_note'])} );""" def _ledger_and_verify( proposal_id: str, applied_by: str, extra_checks: str, approval: dict[str, Any], ) -> str: """Verify canonical rows, then finish the locked, payload-bound apply.""" checks_sql = f"""do $verify$ begin {extra_checks} end $verify$;""" finish_sql = f"""select kb_stage.finish_approved_proposal( {sql_literal(proposal_id)}::uuid, {sql_literal(approval['proposal_type'])}, {_jsonb(approval['payload'])}, {sql_literal(approval['reviewed_by_handle'])}, {sql_literal(approval.get('reviewed_by_agent_id'))}::uuid, {sql_literal(approval['reviewed_at'])}::timestamptz, {sql_literal(approval['review_note'])}, {sql_literal(applied_by)} );""" return checks_sql + "\n" + finish_sql # --------------------------------------------------------------------------- # # Per-type SQL builders (pure; unit-tested without a DB) # # --------------------------------------------------------------------------- # def build_revise_strategy_sql( apply_payload: dict[str, Any], proposal_id: str, applied_by: str | None, approval: dict[str, Any], ) -> str: _validate_approval_meta(approval, "revise_strategy", apply_payload) agent_id = apply_payload.get("agent_id") strategy = apply_payload.get("strategy") or {} nodes: list[dict[str, Any]] = apply_payload.get("strategy_nodes") or [] if not agent_id: raise ValueError("revise_strategy apply_payload requires 'agent_id'") for key in ("diagnosis", "guiding_policy", "proximate_objectives"): if strategy.get(key) is None: raise ValueError(f"revise_strategy apply_payload.strategy requires '{key}'") if not nodes: raise ValueError("revise_strategy apply_payload requires non-empty 'strategy_nodes'") node_values = [] for n in nodes: nt = n.get("node_type") if nt not in ("diagnosis", "policy", "objective"): raise ValueError(f"invalid strategy node_type: {nt!r}") if not n.get("title") or n.get("body") is None: raise ValueError("each strategy_node requires 'title' and 'body'") node_values.append( f"({sql_literal(agent_id)}::uuid, {sql_literal(nt)}, " f"{sql_literal(n['title'])}, {sql_literal(n['body'])}, " f"{int(n.get('rank', 1))})" ) node_values_sql = ",\n ".join(node_values) canonical = f""" -- deactivate the current active strategy for this agent (one_active invariant) update public.strategies set active = false where agent_id = {sql_literal(agent_id)}::uuid and active; -- insert the new strategy as the next version, active insert into public.strategies (agent_id, diagnosis, guiding_policy, proximate_objectives, version, active) select {sql_literal(agent_id)}::uuid, {sql_literal(strategy['diagnosis'])}, {sql_literal(strategy['guiding_policy'])}, {_jsonb(strategy['proximate_objectives'])}, coalesce(max(version), 0) + 1, true from public.strategies where agent_id = {sql_literal(agent_id)}::uuid; -- retire the agent's current strategy nodes (shared NULL-agent nodes untouched) update public.strategy_nodes set status = 'retired', updated_at = now() where agent_id = {sql_literal(agent_id)}::uuid and status <> 'retired'; -- insert the new node set insert into public.strategy_nodes (agent_id, node_type, title, body, rank) values {node_values_sql}; """.rstrip() checks = f""" if (select count(*) from public.strategies where agent_id = {sql_literal(agent_id)}::uuid and active) <> 1 then raise exception 'apply_proposal: expected exactly one active strategy for agent %', {sql_literal(agent_id)}; end if;""" ledger = _ledger_and_verify( proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval ) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) def build_add_edge_sql( apply_payload: dict[str, Any], proposal_id: str, applied_by: str | None, approval: dict[str, Any], ) -> str: _validate_approval_meta(approval, "add_edge", apply_payload) f = apply_payload.get("from_claim") t = apply_payload.get("to_claim") et = apply_payload.get("edge_type") w = apply_payload.get("weight") if not (f and t and et): raise ValueError("add_edge apply_payload requires 'from_claim', 'to_claim', 'edge_type'") if f == t: raise ValueError("add_edge from_claim and to_claim must differ") edge_lock_key = f"claim_edge:{f}:{t}:{et}" canonical = f""" -- serialize semantic edge creation because the historical table has no -- (from_claim,to_claim,edge_type) uniqueness constraint. select pg_advisory_xact_lock(hashtextextended({sql_literal(edge_lock_key)}, 0)); -- insert the edge unless an identical (from,to,type) edge already exists insert into public.claim_edges (from_claim, to_claim, edge_type, weight) select {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, {sql_literal(et)}::edge_type, {sql_literal(w)} where not exists ( select 1 from public.claim_edges where from_claim = {sql_literal(f)}::uuid and to_claim = {sql_literal(t)}::uuid and edge_type = {sql_literal(et)}::edge_type); """.rstrip() checks = f""" if exists (select 1 from public.claim_edges where from_claim = {sql_literal(f)}::uuid and to_claim = {sql_literal(t)}::uuid and edge_type = {sql_literal(et)}::edge_type and weight is distinct from {sql_literal(w)}) then raise exception 'apply_proposal: claim_edge row does not match strict apply payload'; end if; if not exists (select 1 from public.claim_edges where from_claim = {sql_literal(f)}::uuid and to_claim = {sql_literal(t)}::uuid and edge_type = {sql_literal(et)}::edge_type and weight is not distinct from {sql_literal(w)}) then raise exception 'apply_proposal: claim_edge row does not match strict apply payload'; end if;""" ledger = _ledger_and_verify( proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval ) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) def build_attach_evidence_sql( apply_payload: dict[str, Any], proposal_id: str, applied_by: str | None, approval: dict[str, Any], ) -> str: _validate_approval_meta(approval, "attach_evidence", apply_payload) evidence: list[dict[str, Any]] = apply_payload.get("evidence") or [] if not evidence: raise ValueError("attach_evidence apply_payload requires non-empty 'evidence'") statements = [] checks = [] for i, ev in enumerate(evidence): claim_id = ev.get("claim_id") source_id = ev.get("source_id") role = ev.get("role") or "grounds" weight = ev.get("weight") if not claim_id: raise ValueError(f"evidence[{i}] requires 'claim_id'") if not source_id: raise ValueError( f"evidence[{i}] requires an existing 'source_id'. " "kb_apply cannot create public.sources; mint the source upstream first." ) statements.append( f"""insert into public.claim_evidence (claim_id, source_id, role, weight) select {sql_literal(claim_id)}::uuid, {sql_literal(source_id)}::uuid, {sql_literal(role)}::evidence_role, {sql_literal(weight)} on conflict (claim_id, source_id, role) do nothing;""" ) checks.append( f""" if exists (select 1 from public.claim_evidence where claim_id = {sql_literal(claim_id)}::uuid and source_id = {sql_literal(source_id)}::uuid and role = {sql_literal(role)}::evidence_role and weight is distinct from {sql_literal(weight)}) then raise exception 'apply_proposal: evidence row % does not match strict apply payload', {i}; end if; if not exists (select 1 from public.claim_evidence where claim_id = {sql_literal(claim_id)}::uuid and source_id = {sql_literal(source_id)}::uuid and role = {sql_literal(role)}::evidence_role and weight is not distinct from {sql_literal(weight)}) then raise exception 'apply_proposal: evidence row % does not match strict apply payload', {i}; end if;""" ) canonical = "\n".join(statements) ledger = _ledger_and_verify( proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval ) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) def build_approve_claim_sql( apply_payload: dict[str, Any], proposal_id: str, applied_by: str | None, approval: dict[str, Any], ) -> str: """Build one conflict-safe transaction for a reviewed canonical graph bundle.""" _validate_approval_meta(approval, "approve_claim", apply_payload) _reject_unknown( apply_payload, { "contract_version", "source_proposal_id", "agent_id", "claims", "sources", "evidence", "edges", "reasoning_tools", "normalization_manifest", }, "approve_claim apply_payload", ) if apply_payload.get("contract_version") != 2: raise ValueError("approve_claim apply_payload.contract_version must equal 2") for key in ("claims", "sources", "evidence", "edges", "reasoning_tools"): if key not in apply_payload: raise ValueError(f"approve_claim apply_payload requires explicit '{key}' collection") if apply_payload.get("source_proposal_id") is not None: _uuid(apply_payload["source_proposal_id"], "approve_claim.source_proposal_id") if apply_payload.get("normalization_manifest") is not None and not isinstance( apply_payload["normalization_manifest"], dict ): raise ValueError("approve_claim.normalization_manifest must be an object") agent_id = _uuid(apply_payload.get("agent_id"), "approve_claim.agent_id", nullable=True) raw_claims = _rows(apply_payload, "claims", required=True) raw_sources = _rows(apply_payload, "sources") raw_evidence = _rows(apply_payload, "evidence") raw_edges = _rows(apply_payload, "edges") raw_tools = _rows(apply_payload, "reasoning_tools") claims: list[dict[str, Any]] = [] for index, row in enumerate(raw_claims): path = f"approve_claim.claims[{index}]" _reject_unknown( row, {"id", "type", "text", "status", "confidence", "tags", "created_by", "superseded_by"}, path, ) claim_type = row.get("type") if claim_type not in CLAIM_TYPES: raise ValueError(f"{path}.type must be one of {sorted(CLAIM_TYPES)}") text = row.get("text") if not isinstance(text, str) or not text.strip(): raise ValueError(f"{path}.text must be a non-empty string") status = row.get("status", "open") if status != "open": raise ValueError(f"{path}.status must be 'open' for an insert-only approval") tags = row.get("tags") or [] if not isinstance(tags, list) or any(not isinstance(tag, str) for tag in tags): raise ValueError(f"{path}.tags must be a list of strings") claims.append( { "id": _uuid(row.get("id"), f"{path}.id"), "type": claim_type, "text": text, "status": status, "confidence": _weight(row.get("confidence"), f"{path}.confidence"), "tags": tags, "created_by": _uuid( row.get("created_by", agent_id), f"{path}.created_by", nullable=True ), "superseded_by": _uuid( row.get("superseded_by"), f"{path}.superseded_by", nullable=True ), } ) sources: list[dict[str, Any]] = [] for index, row in enumerate(raw_sources): path = f"approve_claim.sources[{index}]" _reject_unknown( row, {"id", "source_type", "url", "storage_path", "excerpt", "hash", "created_by"}, path, ) source_type = row.get("source_type") if source_type not in SOURCE_TYPES: raise ValueError(f"{path}.source_type must be one of {sorted(SOURCE_TYPES)}") source_hash = row.get("hash") if not isinstance(source_hash, str) or not source_hash.strip(): raise ValueError(f"{path}.hash must be a non-empty string") for key in ("url", "storage_path", "excerpt"): if row.get(key) is not None and not isinstance(row.get(key), str): raise ValueError(f"{path}.{key} must be a string or null") sources.append( { "id": _uuid(row.get("id"), f"{path}.id"), "source_type": source_type, "url": row.get("url"), "storage_path": row.get("storage_path"), "excerpt": row.get("excerpt"), "hash": source_hash, "created_by": _uuid( row.get("created_by", agent_id), f"{path}.created_by", nullable=True ), } ) evidence: list[dict[str, Any]] = [] for index, row in enumerate(raw_evidence): path = f"approve_claim.evidence[{index}]" _reject_unknown( row, {"claim_id", "source_id", "role", "weight", "created_by"}, path, ) role = row.get("role", "grounds") if role not in EVIDENCE_ROLES: raise ValueError(f"{path}.role must be one of {sorted(EVIDENCE_ROLES)}") evidence.append( { "claim_id": _uuid(row.get("claim_id"), f"{path}.claim_id"), "source_id": _uuid(row.get("source_id"), f"{path}.source_id"), "role": role, "weight": _weight(row.get("weight"), f"{path}.weight"), "created_by": _uuid( row.get("created_by", agent_id), f"{path}.created_by", nullable=True ), } ) edges: list[dict[str, Any]] = [] for index, row in enumerate(raw_edges): path = f"approve_claim.edges[{index}]" _reject_unknown( row, {"from_claim", "to_claim", "edge_type", "weight", "created_by"}, path, ) edge_type = row.get("edge_type") if edge_type not in EDGE_TYPES: raise ValueError(f"{path}.edge_type must be one of {sorted(EDGE_TYPES)}") from_claim = _uuid(row.get("from_claim"), f"{path}.from_claim") to_claim = _uuid(row.get("to_claim"), f"{path}.to_claim") if from_claim == to_claim: raise ValueError(f"{path} cannot be a self-edge") edges.append( { "from_claim": from_claim, "to_claim": to_claim, "edge_type": edge_type, "weight": _weight(row.get("weight"), f"{path}.weight"), "created_by": _uuid( row.get("created_by", agent_id), f"{path}.created_by", nullable=True ), } ) reasoning_tools: list[dict[str, Any]] = [] for index, row in enumerate(raw_tools): path = f"approve_claim.reasoning_tools[{index}]" _reject_unknown( row, {"id", "agent_id", "name", "description", "category"}, path, ) name = row.get("name") description = row.get("description") category = row.get("category") if not isinstance(name, str) or not name.strip(): raise ValueError(f"{path}.name must be a non-empty string") if not isinstance(description, str) or not description.strip(): raise ValueError(f"{path}.description must be a non-empty string") if category is not None and not isinstance(category, str): raise ValueError(f"{path}.category must be a string or null") reasoning_tools.append( { "id": _uuid(row.get("id"), f"{path}.id"), "agent_id": _uuid(row.get("agent_id", agent_id), f"{path}.agent_id", nullable=True), "name": name, "description": description, "category": category, } ) _dedupe([row["id"] for row in claims], "approve_claim.claims ids") _dedupe([row["id"] for row in sources], "approve_claim.sources ids") _dedupe([row["hash"] for row in sources], "approve_claim.sources hashes") _dedupe( [[row["claim_id"], row["source_id"], row["role"]] for row in evidence], "approve_claim.evidence keys", ) _dedupe( [[row["from_claim"], row["to_claim"], row["edge_type"]] for row in edges], "approve_claim.edges keys", ) _dedupe([row["id"] for row in reasoning_tools], "approve_claim.reasoning_tools ids") statements: list[str] = [] checks: list[str] = [] if sources: collision_checks = [] for row in sources: collision_checks.append( f""" if exists (select 1 from public.sources where hash = {sql_literal(row['hash'])} and id <> {sql_literal(row['id'])}::uuid) then raise exception 'approve_claim: source hash collision for source %', {sql_literal(row['id'])}; end if;""" ) statements.append( "do $source_conflicts$\nbegin\n" + "\n".join(collision_checks) + "\nend\n$source_conflicts$;" ) for row in claims: tags = _text_array(row["tags"]) statements.append( f"""insert into public.claims (id, type, text, status, confidence, tags, created_by, superseded_by) select {sql_literal(row['id'])}::uuid, {sql_literal(row['type'])}, {sql_literal(row['text'])}, {sql_literal(row['status'])}, {sql_literal(row['confidence'])}, {tags}, {sql_literal(row['created_by'])}::uuid, {sql_literal(row['superseded_by'])}::uuid on conflict (id) do nothing;""" ) checks.append( f""" if not exists (select 1 from public.claims where id = {sql_literal(row['id'])}::uuid and type = {sql_literal(row['type'])} and text = {sql_literal(row['text'])} and status = {sql_literal(row['status'])} and confidence is not distinct from {sql_literal(row['confidence'])} and tags is not distinct from {tags} and created_by is not distinct from {sql_literal(row['created_by'])}::uuid and superseded_by is not distinct from {sql_literal(row['superseded_by'])}::uuid) then raise exception 'approve_claim: claim row does not match strict apply payload: %', {sql_literal(row['id'])}; end if;""" ) for row in sources: statements.append( f"""insert into public.sources (id, source_type, url, storage_path, excerpt, hash, created_by) select {sql_literal(row['id'])}::uuid, {sql_literal(row['source_type'])}, {sql_literal(row['url'])}, {sql_literal(row['storage_path'])}, {sql_literal(row['excerpt'])}, {sql_literal(row['hash'])}, {sql_literal(row['created_by'])}::uuid on conflict do nothing;""" ) checks.append( f""" if exists (select 1 from public.sources where hash = {sql_literal(row['hash'])} and id <> {sql_literal(row['id'])}::uuid) then raise exception 'approve_claim: source hash collision for source %', {sql_literal(row['id'])}; end if; if not exists (select 1 from public.sources where id = {sql_literal(row['id'])}::uuid and source_type = {sql_literal(row['source_type'])} and url is not distinct from {sql_literal(row['url'])} and storage_path is not distinct from {sql_literal(row['storage_path'])} and excerpt is not distinct from {sql_literal(row['excerpt'])} and hash = {sql_literal(row['hash'])} and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then raise exception 'approve_claim: source row does not match strict apply payload: %', {sql_literal(row['id'])}; end if;""" ) for row in reasoning_tools: statements.append( f"""insert into public.reasoning_tools (id, agent_id, name, description, category) select {sql_literal(row['id'])}::uuid, {sql_literal(row['agent_id'])}::uuid, {sql_literal(row['name'])}, {sql_literal(row['description'])}, {sql_literal(row['category'])} on conflict (id) do nothing;""" ) checks.append( f""" if not exists (select 1 from public.reasoning_tools where id = {sql_literal(row['id'])}::uuid and agent_id is not distinct from {sql_literal(row['agent_id'])}::uuid and name = {sql_literal(row['name'])} and description = {sql_literal(row['description'])} and category is not distinct from {sql_literal(row['category'])}) then raise exception 'approve_claim: reasoning tool row does not match strict apply payload: %', {sql_literal(row['id'])}; end if;""" ) for row in evidence: statements.append( f"""insert into public.claim_evidence (claim_id, source_id, role, weight, created_by) select {sql_literal(row['claim_id'])}::uuid, {sql_literal(row['source_id'])}::uuid, {sql_literal(row['role'])}::evidence_role, {sql_literal(row['weight'])}, {sql_literal(row['created_by'])}::uuid on conflict (claim_id, source_id, role) do nothing;""" ) checks.append( f""" if exists (select 1 from public.claim_evidence where claim_id = {sql_literal(row['claim_id'])}::uuid and source_id = {sql_literal(row['source_id'])}::uuid and role = {sql_literal(row['role'])}::evidence_role and (weight is distinct from {sql_literal(row['weight'])} or created_by is distinct from {sql_literal(row['created_by'])}::uuid)) then raise exception 'approve_claim: evidence row does not match strict apply payload'; end if; if not exists (select 1 from public.claim_evidence where claim_id = {sql_literal(row['claim_id'])}::uuid and source_id = {sql_literal(row['source_id'])}::uuid and role = {sql_literal(row['role'])}::evidence_role and weight is not distinct from {sql_literal(row['weight'])} and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then raise exception 'approve_claim: evidence row does not match strict apply payload'; end if;""" ) for row in edges: edge_lock_key = ( f"claim_edge:{row['from_claim']}:{row['to_claim']}:{row['edge_type']}" ) statements.append( f"""select pg_advisory_xact_lock(hashtextextended({sql_literal(edge_lock_key)}, 0)); insert into public.claim_edges (from_claim, to_claim, edge_type, weight, created_by) select {sql_literal(row['from_claim'])}::uuid, {sql_literal(row['to_claim'])}::uuid, {sql_literal(row['edge_type'])}::edge_type, {sql_literal(row['weight'])}, {sql_literal(row['created_by'])}::uuid where not exists ( select 1 from public.claim_edges where from_claim = {sql_literal(row['from_claim'])}::uuid and to_claim = {sql_literal(row['to_claim'])}::uuid and edge_type = {sql_literal(row['edge_type'])}::edge_type);""" ) checks.append( f""" if exists (select 1 from public.claim_edges where from_claim = {sql_literal(row['from_claim'])}::uuid and to_claim = {sql_literal(row['to_claim'])}::uuid and edge_type = {sql_literal(row['edge_type'])}::edge_type and (weight is distinct from {sql_literal(row['weight'])} or created_by is distinct from {sql_literal(row['created_by'])}::uuid)) then raise exception 'approve_claim: edge row does not match strict apply payload'; end if; if not exists (select 1 from public.claim_edges where from_claim = {sql_literal(row['from_claim'])}::uuid and to_claim = {sql_literal(row['to_claim'])}::uuid and edge_type = {sql_literal(row['edge_type'])}::edge_type and weight is not distinct from {sql_literal(row['weight'])} and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then raise exception 'approve_claim: edge row does not match strict apply payload'; end if;""" ) canonical = "\n\n".join(statements) ledger = _ledger_and_verify( proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval ) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) def _wrap_txn(canonical_sql: str, ledger_sql: str, approval_guard_sql: str) -> str: return ( "begin;\n" "set local standard_conforming_strings = on;\n" f"{approval_guard_sql}\n" f"{canonical_sql}\n" f"{ledger_sql}\n" "commit;\n" ) BUILDERS = { "revise_strategy": build_revise_strategy_sql, "add_edge": build_add_edge_sql, "attach_evidence": build_attach_evidence_sql, "approve_claim": build_approve_claim_sql, } def build_apply_sql(proposal: dict[str, Any], applied_by: str | None) -> str: ptype = proposal["proposal_type"] if ptype not in BUILDERS: raise ValueError(f"apply_proposal cannot apply proposal_type {ptype!r}") payload = proposal.get("payload") or {} apply_payload = payload.get("apply_payload") if apply_payload is None: raise ValueError( "proposal payload has no 'apply_payload' strict contract. " "Run the normalizer to produce apply_payload before applying." ) return BUILDERS[ptype]( apply_payload, proposal["id"], applied_by or SERVICE_AGENT_HANDLE, proposal, ) def assert_applyable(proposal: dict[str, Any]) -> None: status = proposal.get("status") if status == "applied": raise SystemExit(f"proposal {proposal['id']} is already applied (idempotent no-op)") if status != "approved": raise SystemExit( f"proposal {proposal['id']} has status {status!r}; only 'approved' proposals apply" ) # --------------------------------------------------------------------------- # # Execution (docker exec as the kb_apply role) # # --------------------------------------------------------------------------- # def load_password(secrets_file: str) -> str: path = Path(secrets_file) if not path.is_file(): raise SystemExit(f"secrets file not found: {secrets_file}") for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, val = line.partition("=") if key.strip() in ("KB_APPLY_PASSWORD", "KB_APPLY_DB_PASSWORD", "PGPASSWORD"): return val.strip().strip('"').strip("'") raise SystemExit(f"no KB_APPLY_PASSWORD/KB_APPLY_DB_PASSWORD/PGPASSWORD entry in {secrets_file}") def run_psql(args: argparse.Namespace, sql: str, password: str) -> str: command = [ "docker", "exec", "-e", "PGPASSWORD", "-i", args.container, "psql", "-U", args.role, "-h", args.host, "-d", args.db, "-v", "ON_ERROR_STOP=1", "-At", "-q", ] result = subprocess.run( command, input=sql, text=True, capture_output=True, env={"PGPASSWORD": password, "PATH": "/usr/bin:/bin:/usr/local/bin"}, check=False, ) if result.returncode != 0: raise SystemExit( f"psql failed ({result.returncode})\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}" ) return result.stdout def load_proposal(args: argparse.Namespace, password: str) -> dict[str, Any]: sql = f"""select jsonb_build_object( 'id', id::text, 'proposal_type', proposal_type, 'status', status, 'payload', payload, 'reviewed_by_handle', reviewed_by_handle, 'reviewed_by_agent_id', reviewed_by_agent_id::text, 'reviewed_at', reviewed_at::text, 'review_note', review_note)::text from kb_stage.kb_proposals where id = {sql_literal(args.proposal_id)}::uuid;""" out = run_psql(args, sql, password).strip() if not out: raise SystemExit(f"proposal not found: {args.proposal_id}") return json.loads(out) def parse_args(argv: list[str]) -> argparse.Namespace: p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument("proposal_id", help="UUID of the approved proposal to apply") p.add_argument( "--applied-by", default=SERVICE_AGENT_HANDLE, help="agent handle recorded as applied_by_handle and resolved to the " "applied_by_agent_id FK (default: the kb-apply service agent)", ) p.add_argument("--dry-run", action="store_true", help="print the SQL, do not execute") p.add_argument("--secrets-file", default=DEFAULT_SECRETS_FILE) p.add_argument("--container", default=DEFAULT_CONTAINER) p.add_argument("--db", default=DEFAULT_DB) p.add_argument("--host", default=DEFAULT_HOST) p.add_argument("--role", default=DEFAULT_ROLE) return p.parse_args(argv) def main(argv: list[str] | None = None) -> int: args = parse_args(sys.argv[1:] if argv is None else argv) if args.dry_run: # Dry-run still needs the proposal to build SQL; read it as kb_apply. password = load_password(args.secrets_file) proposal = load_proposal(args, password) assert_applyable(proposal) print(build_apply_sql(proposal, args.applied_by)) return 0 password = load_password(args.secrets_file) proposal = load_proposal(args, password) assert_applyable(proposal) sql = build_apply_sql(proposal, args.applied_by) run_psql(args, sql, password) print(f"applied proposal {proposal['id']} ({proposal['proposal_type']})") return 0 if __name__ == "__main__": raise SystemExit(main())