teleo-infrastructure/scripts/kb_rich_proposal_creation_plan.py
twentyOne2x 699a9e7917
Some checks are pending
CI / lint-and-test (push) Waiting to run
Add Working Leo operator skill pack
- Add reusable Teleo/Leo skills for VPS, GCP, Telegram, KB changes, provenance, and handoff onboarding.
- Add guarded KB proposal tooling for approved rich proposals, apply packets, and rollback proof.
- Add Cory-style open-ended benchmark prompts and tests alongside the precise regression canaries.

`.agents/skills/leo-telegram-canary-ops/SKILL.md`
`.agents/skills/teleo-gcp-parity-ops/SKILL.md`
`.agents/skills/teleo-infra-provenance/SKILL.md`
`.agents/skills/teleo-kb-db-change-workflow/SKILL.md`
`.agents/skills/teleo-leo-onboarding/SKILL.md`
`.agents/skills/teleo-proof-handoff/SKILL.md`
`.agents/skills/teleo-vps-runtime-ops/SKILL.md`
`.agents/skills/working-leo-cory-outcomes/SKILL.md`
`docs/reports/leo-working-state-20260709/claim-source-contract-preview-current.md`
`docs/reports/leo-working-state-20260709/cory-expected-working-leo-outcomes-20260709.md`
`docs/reports/leo-working-state-20260709/current-truth-index.md`
`docs/reports/leo-working-state-20260709/fable-leo-teleo-onboarding.md`
`docs/reports/leo-working-state-20260709/gcp-db-parity-current-blocker-20260709.json`
`docs/reports/leo-working-state-20260709/helmer-7powers-creation-plan.json`
`docs/reports/leo-working-state-20260709/kb-apply-canary-execute-current.md`
`docs/reports/leo-working-state-20260709/kb-apply-canary-plan-current.md`
`docs/reports/leo-working-state-20260709/leo-db-state-13-skill-pack.svg`
`docs/reports/leo-working-state-20260709/operator-surface-map.md`
`docs/reports/leo-working-state-20260709/production-apply-packet-current.md`
`docs/reports/leo-working-state-20260709/rich-proposal-creation-plan-mapped-current.md`
`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/telegram-gateway-handler-canary-current.json`
`docs/reports/leo-working-state-20260709/telegram-gateway-handler-canary-current.md`
`docs/reports/leo-working-state-20260709/telegram-live-canary-current.json`
`docs/reports/leo-working-state-20260709/telegram-live-canary-current.md`
`docs/reports/leo-working-state-20260709/telegram-live-db-write-canary-20260709.json`
`docs/reports/leo-working-state-20260709/telegram-live-db-write-canary-20260709.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`
`docs/reports/leo-working-state-20260709/working-leo-open-ended-benchmark-spec.json`
`scripts/approve_proposal.py`
`scripts/kb_claim_source_contract_preview.py`
`scripts/kb_rich_proposal_apply_packet.py`
`scripts/kb_rich_proposal_creation_plan.py`
`scripts/working_leo_open_ended_benchmark.py`
`tests/test_approve_proposal.py`
`tests/test_kb_claim_source_contract_preview.py`
`tests/test_kb_rich_proposal_apply_packet.py`
`tests/test_kb_rich_proposal_creation_plan.py`
`tests/test_working_leo_open_ended_benchmark.py`
2026-07-09 22:56:48 +02:00

718 lines
30 KiB
Python

#!/usr/bin/env python3
"""Build a rollback-only creation plan for rich approved KB proposals.
This is the bridge between old Leo freeform approvals and the strict apply
worker. It is intentionally non-mutating: the output is a deterministic row
contract plus rollback rehearsal SQL, not an apply command.
The planner assigns stable UUIDs to proposed claim/source rows, preserves claim
bodies and caveats as source-backed evidence, emits only edges supported by the
live ``edge_type`` enum, and reports every fragment that still needs a reviewed
mapping. Production apply remains a separate reviewed step after clone/staging
rehearsal.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
import uuid
from pathlib import Path
from typing import Any
LIVE_EDGE_TYPES = {
"supports",
"challenges",
"requires",
"relates",
"contradicts",
"supersedes",
"derives_from",
"cites",
"causes",
"constrains",
"accelerates",
}
LIVE_EVIDENCE_ROLES = {"grounds", "illustrates", "contradicts"}
LIVE_CLAIM_TYPES = {"empirical", "structural", "normative", "meta", "concept"}
LIVE_SOURCE_TYPES = {"paper", "article", "transcript", "observation", "dataset", "post", "dm", "other"}
CLAIM_TYPE_ALIASES = {
"governance": "meta",
"process": "meta",
}
SOURCE_TYPE_ALIASES = {
"book": "other",
"interview_article": "article",
"official_site": "article",
"official_site_foreword": "article",
"podcast_show_notes": "article",
"podcast_summary": "article",
"podcast_transcript": "transcript",
"url": "article",
"document": "other",
"note": "other",
"proposal_body": "other",
}
DEFAULT_EVIDENCE_ROLE = "grounds"
BODY_EVIDENCE_ROLE = "illustrates"
BODY_SOURCE_TYPE = "proposal_body"
def _payload_list(payload: dict[str, Any], key: str) -> list[Any]:
value = payload.get(key)
return value if isinstance(value, list) else []
def _payload_dict(payload: dict[str, Any], key: str) -> dict[str, Any]:
value = payload.get(key)
return value if isinstance(value, dict) else {}
def _claim_candidate_items(payload: dict[str, Any]) -> list[dict[str, Any]]:
items: list[dict[str, Any]] = []
seen: set[str] = set()
for key in ("claim_candidates", "proposed_claims"):
for candidate in _payload_list(payload, key):
if not isinstance(candidate, dict):
continue
claim_key = str(candidate.get("claim_key") or "").strip()
dedupe_key = claim_key or json.dumps(candidate, sort_keys=True, ensure_ascii=False)
if dedupe_key in seen:
continue
seen.add(dedupe_key)
items.append(candidate)
return items
def _stable_uuid(proposal_id: str, kind: str, key: str) -> str:
return str(uuid.uuid5(uuid.NAMESPACE_URL, f"teleo:kb-rich-contract:{proposal_id}:{kind}:{key}"))
def _stable_hash(value: Any) -> str:
payload = json.dumps(value, sort_keys=True, ensure_ascii=False)
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
def _is_uuid(value: Any) -> bool:
try:
uuid.UUID(str(value))
except (TypeError, ValueError):
return False
return True
def _sql_literal(value: Any) -> str:
if value is None:
return "null"
if isinstance(value, bool):
return "true" if value else "false"
if isinstance(value, (int, float)):
return repr(value)
return "'" + str(value).replace("'", "''") + "'"
def _sql_text_array(values: list[Any]) -> str:
if not values:
return "'{}'::text[]"
return "array[" + ", ".join(_sql_literal(str(v)) for v in values) + "]::text[]"
def _compact_text(value: str, max_chars: int = 8000) -> str:
return value if len(value) <= max_chars else value[: max_chars - 3] + "..."
def _source_excerpt(source: dict[str, Any]) -> str:
excerpts = source.get("key_excerpts")
if isinstance(excerpts, list):
parts = [item.get("text", "") if isinstance(item, dict) else str(item) for item in excerpts]
excerpt = "\n\n".join(part for part in parts if part).strip()
else:
excerpt = str(source.get("key_excerpt") or source.get("use") or excerpts or "").strip()
metadata = {
key: source.get(key)
for key in ("title", "source_key", "source_quality", "usage_limits", "claim_id")
if source.get(key) is not None
}
if metadata:
return _compact_text(json.dumps({"metadata": metadata, "excerpt": excerpt}, ensure_ascii=False, sort_keys=True))
return _compact_text(excerpt)
def _source_type(source: dict[str, Any]) -> str:
if source.get("source_type"):
return str(source["source_type"])
if source.get("url"):
return "url"
if source.get("local_text") or source.get("local_snapshot") or source.get("storage_path"):
return "document"
return "note"
def _normalize_source_type(raw_type: Any) -> tuple[str, dict[str, Any] | None]:
raw_text = str(raw_type or "other").strip() or "other"
if raw_text in LIVE_SOURCE_TYPES:
return raw_text, None
mapped = SOURCE_TYPE_ALIASES.get(raw_text, "other")
return mapped, {"from": raw_text, "to": mapped, "reason": "proposal source_type collapsed to live sources.source_type"}
def _claim_text(candidate: dict[str, Any]) -> str:
headline = str(candidate.get("headline") or "").strip()
body = str(candidate.get("body") or "").strip()
text = str(candidate.get("text") or "").strip()
return headline or body or text
def _normalize_claim_type(raw_type: Any) -> tuple[str, dict[str, Any] | None]:
raw_text = str(raw_type or "structural").strip() or "structural"
if raw_text in LIVE_CLAIM_TYPES:
return raw_text, None
parts = [part.strip() for part in raw_text.replace(":", "/").split("/") if part.strip()]
for part in parts:
if part in LIVE_CLAIM_TYPES:
return part, {"from": raw_text, "to": part, "reason": "proposal subtype collapsed to live claims.type"}
if part in CLAIM_TYPE_ALIASES:
mapped = CLAIM_TYPE_ALIASES[part]
return mapped, {"from": raw_text, "to": mapped, "reason": "proposal governance/process subtype mapped to meta"}
return "meta", {"from": raw_text, "to": "meta", "reason": "unsupported proposal type requires review; meta used for rehearsal"}
def _claim_body_excerpt(proposal: dict[str, Any], candidate: dict[str, Any]) -> str:
body_packet = {
"proposal_id": proposal.get("id"),
"source_ref": proposal.get("source_ref") or (proposal.get("payload") or {}).get("source_ref"),
"reviewed_by_handle": proposal.get("reviewed_by_handle"),
"review_note": proposal.get("review_note"),
"claim_key": candidate.get("claim_key"),
"headline": candidate.get("headline"),
"body": candidate.get("body"),
"text": candidate.get("text"),
"candidate_review_note": candidate.get("review_note"),
"evidence_tier": candidate.get("evidence_tier"),
"needs_research": candidate.get("needs_research"),
"evidence": candidate.get("evidence"),
"edges": candidate.get("edges"),
}
return _compact_text(json.dumps(body_packet, ensure_ascii=False, sort_keys=True))
def _candidate_claim_rows(proposal: dict[str, Any], payload: dict[str, Any]) -> tuple[list[dict[str, Any]], dict[str, str]]:
rows: list[dict[str, Any]] = []
key_to_id: dict[str, str] = {}
proposal_id = str(proposal.get("id") or "")
created_by = proposal.get("proposed_by_agent_id")
for candidate in _claim_candidate_items(payload):
claim_key = str(candidate.get("claim_key") or "").strip()
if not claim_key:
continue
claim_id = _stable_uuid(proposal_id, "claim", claim_key)
raw_type = candidate.get("type") or candidate.get("node_kind") or "structural"
claim_type, type_normalization = _normalize_claim_type(raw_type)
key_to_id[claim_key] = claim_id
rows.append(
{
"id": claim_id,
"claim_key": claim_key,
"type": claim_type,
"original_type": raw_type,
"type_normalization": type_normalization,
"text": _claim_text(candidate),
"status": "open",
"confidence": candidate.get("confidence"),
"tags": candidate.get("tags") or [],
"created_by": created_by,
}
)
return rows, key_to_id
def _source_rows(
proposal: dict[str, Any], payload: dict[str, Any], claim_rows: list[dict[str, Any]]
) -> tuple[list[dict[str, Any]], dict[str, str], dict[str, str]]:
proposal_id = str(proposal.get("id") or "")
created_by = proposal.get("proposed_by_agent_id")
rows: list[dict[str, Any]] = []
source_key_to_id: dict[str, str] = {}
body_source_by_claim_key: dict[str, str] = {}
for source in _payload_list(payload, "source_candidates"):
if not isinstance(source, dict):
continue
source_key = str(source.get("source_key") or "").strip()
if not source_key:
continue
source_id = _stable_uuid(proposal_id, "source", source_key)
source_key_to_id[source_key] = source_id
excerpt = _source_excerpt(source)
raw_source_type = _source_type(source)
source_type, source_type_normalization = _normalize_source_type(raw_source_type)
row = {
"id": source_id,
"source_key": source_key,
"source_type": source_type,
"original_source_type": raw_source_type,
"source_type_normalization": source_type_normalization,
"url": source.get("url"),
"storage_path": source.get("local_text") or source.get("local_snapshot") or source.get("storage_path"),
"excerpt": excerpt,
"hash": _stable_hash({"proposal_id": proposal_id, "source_key": source_key, "excerpt": excerpt}),
"created_by": created_by,
}
rows.append(row)
claim_candidates = {str(candidate.get("claim_key") or ""): candidate for candidate in _claim_candidate_items(payload)}
for claim in claim_rows:
claim_key = claim["claim_key"]
candidate = claim_candidates.get(claim_key)
if not candidate:
continue
source_key = f"{claim_key}__proposal_body"
source_id = _stable_uuid(proposal_id, "claim-body-source", claim_key)
body_source_by_claim_key[claim_key] = source_id
excerpt = _claim_body_excerpt(proposal, candidate)
source_type, source_type_normalization = _normalize_source_type(BODY_SOURCE_TYPE)
rows.append(
{
"id": source_id,
"source_key": source_key,
"source_type": source_type,
"original_source_type": BODY_SOURCE_TYPE,
"source_type_normalization": source_type_normalization,
"url": None,
"storage_path": None,
"excerpt": excerpt,
"hash": _stable_hash({"proposal_id": proposal_id, "source_key": source_key, "excerpt": excerpt}),
"created_by": created_by,
}
)
return rows, source_key_to_id, body_source_by_claim_key
def _resolve_claim_ref(ref: Any, claim_key_to_id: dict[str, str]) -> str | None:
if not ref:
return None
ref_text = str(ref)
return claim_key_to_id.get(ref_text) or ref_text
def _mapping_section(mappings: dict[str, Any] | None, key: str) -> dict[str, Any]:
if not mappings:
return {}
value = mappings.get(key)
return value if isinstance(value, dict) else {}
def _mapping_value(raw: Any, *, original: Any, default_reason: str) -> tuple[Any, dict[str, Any] | None]:
if raw is None:
return original, None
if isinstance(raw, dict):
mapped = raw.get("to") or raw.get("claim_id") or raw.get("target") or raw.get("edge_type")
if mapped is None:
return original, None
return mapped, {"from": original, "to": mapped, "reason": raw.get("reason") or default_reason}
return raw, {"from": original, "to": raw, "reason": default_reason}
def _map_lookup(
mappings: dict[str, Any] | None, section: str, original: Any, *, default_reason: str
) -> tuple[Any, dict[str, Any] | None]:
if original is None:
return original, None
raw = _mapping_section(mappings, section).get(str(original))
return _mapping_value(raw, original=original, default_reason=default_reason)
def _edge_fragments(payload: dict[str, Any]) -> list[tuple[dict[str, Any], str | None]]:
fragments: list[tuple[dict[str, Any], str | None]] = []
for edge in _payload_list(payload, "proposed_supersession_edges"):
if isinstance(edge, dict):
fragments.append((edge, edge.get("from") or edge.get("from_claim_key")))
for candidate in _claim_candidate_items(payload):
default_from = candidate.get("claim_key")
for edge in _payload_list(candidate, "edges"):
if isinstance(edge, dict):
fragments.append((edge, default_from))
return fragments
def _plan_edges(
payload: dict[str, Any],
claim_key_to_id: dict[str, str],
mappings: dict[str, Any] | None = None,
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
planned: list[dict[str, Any]] = []
blocked: list[dict[str, Any]] = []
seen: set[tuple[str, str, str]] = set()
for edge, default_from_key in _edge_fragments(payload):
original_edge_type = edge.get("edge_type")
edge_type, edge_type_mapping = _map_lookup(
mappings,
"edge_type_aliases",
original_edge_type,
default_reason="reviewed mapping overlay converted proposal edge_type to live edge_type",
)
from_ref = edge.get("from_claim") or edge.get("from_claim_id") or edge.get("from") or default_from_key
target = (
edge.get("to_claim")
or edge.get("to_claim_id")
or edge.get("target_claim_id")
or edge.get("to_existing_claim")
or edge.get("target")
)
mapped_target, target_mapping = _map_lookup(
mappings,
"target_claim_mappings",
target,
default_reason="reviewed mapping overlay converted external/context target to canonical claim",
)
from_claim = _resolve_claim_ref(from_ref, claim_key_to_id)
to_claim = _resolve_claim_ref(mapped_target, claim_key_to_id)
reasons = []
if edge_type not in LIVE_EDGE_TYPES:
reasons.append(f"unsupported edge_type {edge_type!r}")
if not from_claim:
reasons.append("missing from_claim")
if not to_claim:
reasons.append("missing to_claim")
if from_claim and not _is_uuid(from_claim):
reasons.append("from_claim does not resolve to a UUID")
if to_claim and not _is_uuid(to_claim):
reasons.append("to_claim does not resolve to a UUID")
if (
target
and str(target) not in claim_key_to_id
and not _is_uuid(target)
and not edge.get("target_claim_id")
and not edge.get("to_claim_id")
and target_mapping is None
):
reasons.append("target is external/context text and needs a reviewed canonical mapping")
if from_claim and to_claim and from_claim == to_claim:
reasons.append("self-edge refused")
if reasons:
blocked.append(
{
"kind": "edge",
"fragment": edge,
"default_from_key": default_from_key,
"edge_type_mapping": edge_type_mapping,
"target_mapping": target_mapping,
"resolved": {"from_claim": from_claim, "to_claim": to_claim, "edge_type": edge_type},
"reasons": reasons,
}
)
continue
key = (from_claim, to_claim, str(edge_type))
if key in seen:
continue
seen.add(key)
planned.append(
{
"from_claim": from_claim,
"to_claim": to_claim,
"edge_type": edge_type,
"weight": edge.get("weight"),
"edge_type_original": original_edge_type,
"edge_type_mapping": edge_type_mapping,
"target_original": target,
"target_mapping": target_mapping,
"source_fragment": edge,
}
)
return planned, blocked
def _plan_evidence(
payload: dict[str, Any],
claim_key_to_id: dict[str, str],
source_key_to_id: dict[str, str],
body_source_by_claim_key: dict[str, str],
) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
planned: list[dict[str, Any]] = []
blocked: list[dict[str, Any]] = []
seen: set[tuple[str, str, str]] = set()
def add_evidence(claim_id: str, source_id: str, role: str, fragment: dict[str, Any]) -> None:
key = (claim_id, source_id, role)
if key not in seen:
seen.add(key)
planned.append({"claim_id": claim_id, "source_id": source_id, "role": role, "weight": None, "source_fragment": fragment})
for candidate in _claim_candidate_items(payload):
claim_key = str(candidate.get("claim_key") or "")
claim_id = claim_key_to_id.get(claim_key)
if not claim_id:
continue
body_source_id = body_source_by_claim_key.get(claim_key)
if body_source_id:
add_evidence(claim_id, body_source_id, BODY_EVIDENCE_ROLE, {"source_key": f"{claim_key}__proposal_body"})
for evidence in _payload_list(candidate, "evidence"):
if isinstance(evidence, dict):
source_key = evidence.get("source_key")
source_id = evidence.get("source_id") or source_key_to_id.get(str(source_key or ""))
role = evidence.get("role") or DEFAULT_EVIDENCE_ROLE
fragment = evidence
else:
source_key = str(evidence)
source_id = source_key_to_id.get(source_key)
role = DEFAULT_EVIDENCE_ROLE
fragment = {"source_key": source_key}
reasons = []
if role not in LIVE_EVIDENCE_ROLES:
reasons.append(f"unsupported evidence_role {role!r}")
if not source_id:
reasons.append("missing source_id; source_key not present in source_candidates")
if reasons:
blocked.append({"kind": "evidence", "claim_key": claim_key, "fragment": fragment, "reasons": reasons})
continue
add_evidence(claim_id, source_id, role, fragment)
return planned, blocked
def _sanitize_framework_powers(powers: Any) -> tuple[Any, list[dict[str, Any]]]:
if not isinstance(powers, list):
return powers, []
sanitized: list[Any] = []
deferred: list[dict[str, Any]] = []
for power in powers:
if not isinstance(power, dict):
sanitized.append(power)
continue
power_copy = dict(power)
examples = power_copy.get("examples")
if isinstance(examples, list):
kept_examples: list[Any] = []
for example in examples:
if "teleo" in str(example).lower():
deferred.append(
{
"power": power_copy.get("name"),
"reason": "Teleo application examples are deferred from the framework canonical row.",
}
)
else:
kept_examples.append(example)
power_copy["examples"] = kept_examples
sanitized.append(power_copy)
return sanitized, deferred
def _sanitize_conceptual_neighborhood(neighborhood: Any) -> tuple[Any, int]:
if not isinstance(neighborhood, dict):
return neighborhood, 0
clean = dict(neighborhood)
candidates = clean.get("additional_moat_neighborhood_candidates")
removed = 0
if isinstance(candidates, list):
kept: list[Any] = []
for candidate in candidates:
if isinstance(candidate, dict) and str(candidate.get("edge_type") or "").lower() == "defer":
removed += 1
continue
kept.append(candidate)
clean["additional_moat_neighborhood_candidates"] = kept
return clean, removed
def _reasoning_tool_rows(proposal: dict[str, Any], payload: dict[str, Any]) -> list[dict[str, Any]]:
tool = _payload_dict(payload, "proposed_reasoning_tool_node")
name = str(tool.get("name") or "").strip()
if not name:
return []
proposal_id = str(proposal.get("id") or "")
sanitized_powers, deferred_examples = _sanitize_framework_powers(payload.get("seven_powers"))
sanitized_neighborhood, deferred_neighborhood_count = _sanitize_conceptual_neighborhood(
payload.get("conceptual_neighborhood")
)
description_packet = {
"summary": tool.get("summary"),
"core_test": tool.get("core_test"),
"use_when": tool.get("use_when"),
"do_not_use_when": tool.get("do_not_use_when"),
"position_in_leo_strategy_stack": tool.get("position_in_leo_strategy_stack"),
"review_gate": tool.get("review_gate"),
"seven_powers": sanitized_powers,
"power_lifecycle_map": payload.get("power_lifecycle_map"),
"conceptual_neighborhood": sanitized_neighborhood,
"approval_change_log": payload.get("approval_change_log"),
"deferred_application_examples_removed": len(deferred_examples),
"deferred_neighborhood_candidates_removed": deferred_neighborhood_count,
"deferred_boundary": (
"Teleo-specific application/self-positioning examples are not canonicalized in this reasoning_tool row; "
"stage them separately if reopened and approved."
),
"source_ref": proposal.get("source_ref") or payload.get("source_ref"),
"review_note": proposal.get("review_note"),
}
category = str(tool.get("category") or "strategy_framework").strip() or "strategy_framework"
return [
{
"id": _stable_uuid(proposal_id, "reasoning-tool", name.lower()),
"agent_id": proposal.get("proposed_by_agent_id"),
"name": name,
"description": _compact_text(json.dumps(description_packet, ensure_ascii=False, sort_keys=True), 20000),
"category": category,
"source_fragment": tool,
}
]
def _reasoning_tool_insert_sql(row: dict[str, Any]) -> str:
return 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'])}
where not exists (select 1 from public.reasoning_tools where id = {_sql_literal(row['id'])}::uuid);"""
def _claim_insert_sql(row: dict[str, Any]) -> str:
return f"""insert into public.claims (id, type, text, status, confidence, tags, created_by)
select {_sql_literal(row['id'])}::uuid, {_sql_literal(row['type'])}, {_sql_literal(row['text'])},
{_sql_literal(row['status'])}, {_sql_literal(row['confidence'])}, {_sql_text_array(row['tags'])},
{_sql_literal(row['created_by'])}::uuid
where not exists (select 1 from public.claims where id = {_sql_literal(row['id'])}::uuid);"""
def _source_insert_sql(row: dict[str, Any]) -> str:
return 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
where not exists (select 1 from public.sources where id = {_sql_literal(row['id'])}::uuid);"""
def _edge_insert_sql(row: dict[str, Any]) -> str:
return f"""insert into public.claim_edges (from_claim, to_claim, edge_type, weight)
select {_sql_literal(row['from_claim'])}::uuid, {_sql_literal(row['to_claim'])}::uuid,
{_sql_literal(row['edge_type'])}::edge_type, {_sql_literal(row['weight'])}
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);"""
def _evidence_insert_sql(row: dict[str, Any]) -> str:
return f"""insert into public.claim_evidence (claim_id, source_id, role, weight)
select {_sql_literal(row['claim_id'])}::uuid, {_sql_literal(row['source_id'])}::uuid,
{_sql_literal(row['role'])}::evidence_role, {_sql_literal(row['weight'])}
where 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);"""
def build_rollback_sql(plan: dict[str, Any]) -> str:
statements: list[str] = [
"begin;",
"set local statement_timeout = '30s';",
"-- rollback-only rehearsal: do not replace ROLLBACK with COMMIT without separate review",
]
statements.extend(_reasoning_tool_insert_sql(row) for row in plan["planned_rows"]["reasoning_tools"])
statements.extend(_claim_insert_sql(row) for row in plan["planned_rows"]["claims"])
statements.extend(_source_insert_sql(row) for row in plan["planned_rows"]["sources"])
statements.extend(_evidence_insert_sql(row) for row in plan["planned_rows"]["claim_evidence"])
statements.extend(_edge_insert_sql(row) for row in plan["planned_rows"]["claim_edges"])
statements.append("rollback;")
return "\n\n".join(statements) + "\n"
def build_creation_plan(proposal: dict[str, Any], mappings: dict[str, Any] | None = None) -> dict[str, Any]:
payload = proposal.get("payload") if isinstance(proposal.get("payload"), dict) else {}
proposal_id = str(proposal.get("id") or "")
claim_rows, claim_key_to_id = _candidate_claim_rows(proposal, payload)
source_rows, source_key_to_id, body_source_by_claim_key = _source_rows(proposal, payload, claim_rows)
edge_rows, blocked_edges = _plan_edges(payload, claim_key_to_id, mappings)
evidence_rows, blocked_evidence = _plan_evidence(payload, claim_key_to_id, source_key_to_id, body_source_by_claim_key)
reasoning_tool_rows = _reasoning_tool_rows(proposal, payload)
old_claim = _payload_dict(payload, "old_claim")
concept_map = _payload_dict(payload, "proposed_concept_map_dependency")
unresolved_design_decisions: list[dict[str, Any]] = []
if old_claim.get("id") and len(claim_rows) > 1:
unresolved_design_decisions.append(
{
"decision": "many_successor_supersession",
"reason": "public.claims.superseded_by stores one UUID, but this proposal splits one claim into multiple successors.",
"safe_default": "Do not update public.claims.superseded_by in this plan; use reviewed edges or an aggregate successor node.",
}
)
if concept_map:
unresolved_design_decisions.append(
{
"decision": "concept_map_storage",
"reason": "The proposal includes a concept-map node but the live canonical claim/source contract has no first-class concept-map table in this apply lane.",
"safe_default": "Keep concept-map content as source-backed context until a reviewed schema lane exists.",
}
)
plan = {
"proposal_id": proposal_id,
"status": proposal.get("status"),
"proposal_type": proposal.get("proposal_type"),
"reviewed_by_handle": proposal.get("reviewed_by_handle"),
"read_only": True,
"runtime_change": False,
"direct_production_apply_ready": False,
"rehearsal_scope": "rollback_only_sql",
"mapping_overlay_applied": bool(mappings),
"mapping_overlay": mappings or {},
"generated_ids": {
"reasoning_tools": {row["name"]: row["id"] for row in reasoning_tool_rows},
"claims": claim_key_to_id,
"sources": source_key_to_id,
"claim_body_sources": body_source_by_claim_key,
},
"planned_rows": {
"reasoning_tools": reasoning_tool_rows,
"claims": claim_rows,
"sources": source_rows,
"claim_evidence": evidence_rows,
"claim_edges": edge_rows,
},
"blocked_fragments": blocked_edges + blocked_evidence,
"unresolved_design_decisions": unresolved_design_decisions,
"next_action": (
"Run rollback SQL in a clone/staging DB, review blocked edge/context mappings, then create strict child "
"proposals or a separately approved claim/source creation apply tool. Do not apply this directly to production."
),
}
plan["rollback_rehearsal_sql"] = build_rollback_sql(plan)
return plan
def load_from_input(path: str) -> list[dict[str, Any]]:
data = json.loads(Path(path).read_text(encoding="utf-8"))
if isinstance(data, list):
return data
if isinstance(data, dict) and isinstance(data.get("proposals"), list):
return data["proposals"]
if isinstance(data, dict):
return [data]
raise SystemExit(f"unsupported proposal JSON shape in {path}")
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--input-json", required=True, help="proposal JSON or list of proposal JSON objects")
parser.add_argument("--mapping-json", help="optional reviewed mapping overlay for edge types and external targets")
parser.add_argument("--sql-out", help="optional path for concatenated rollback rehearsal SQL")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(sys.argv[1:] if argv is None else argv)
mappings = json.loads(Path(args.mapping_json).read_text(encoding="utf-8")) if args.mapping_json else None
plans = [build_creation_plan(proposal, mappings) for proposal in load_from_input(args.input_json)]
if args.sql_out:
Path(args.sql_out).write_text("\n\n".join(plan["rollback_rehearsal_sql"] for plan in plans), encoding="utf-8")
print(json.dumps({"creation_plans": plans}, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())