Pin reviewed Leoclean runtime SQL source

This commit is contained in:
twentyOne2x 2026-07-22 03:16:58 +02:00
parent 87580c219f
commit 6e4287987a
2 changed files with 59 additions and 4 deletions

View file

@ -12,6 +12,7 @@ from __future__ import annotations
import argparse import argparse
import ast import ast
import hashlib
import json import json
import re import re
from collections import defaultdict from collections import defaultdict
@ -47,6 +48,7 @@ DYNAMIC_SQL_VALUE = "__teleo_dynamic_sql_value__"
UNREVIEWED_DYNAMIC_SQL = "__teleo_unreviewed_dynamic_sql__" UNREVIEWED_DYNAMIC_SQL = "__teleo_unreviewed_dynamic_sql__"
REVIEWED_SQL_INTERPOLATION_CALLS = frozenset({"sql_array", "sql_integer", "sql_json_array", "sql_literal"}) REVIEWED_SQL_INTERPOLATION_CALLS = frozenset({"sql_array", "sql_integer", "sql_json_array", "sql_literal"})
QUERY_RUNNERS = frozenset({"run_psql", "psql_json_lines"}) QUERY_RUNNERS = frozenset({"run_psql", "psql_json_lines"})
REVIEWED_RUNTIME_SOURCE_SHA256 = "5f98218622803e0a1bbedc1e23e538d1f152ecb516e31a387a5eb39080aaf2cd"
DYNAMIC_CALL_BUILTINS = frozenset({"eval", "exec", "globals", "locals"}) DYNAMIC_CALL_BUILTINS = frozenset({"eval", "exec", "globals", "locals"})
READ_CAPABILITY = "read" READ_CAPABILITY = "read"
STAGE_PROPOSAL_CAPABILITY = "stage-proposal" STAGE_PROPOSAL_CAPABILITY = "stage-proposal"
@ -83,6 +85,15 @@ class QueryContractError(ValueError):
"""The runtime SQL cannot be safely reduced to an exact read contract.""" """The runtime SQL cannot be safely reduced to an exact read contract."""
def _assert_exact_runtime_source(source: str) -> None:
observed = hashlib.sha256(source.encode("utf-8")).hexdigest()
if observed != REVIEWED_RUNTIME_SOURCE_SHA256:
raise QueryContractError(
"runtime query surface differs from the exact reviewed runtime source: "
f"expected={REVIEWED_RUNTIME_SOURCE_SHA256}, observed={observed}"
)
def _is_reviewed_sql_interpolation(node: ast.expr) -> bool: def _is_reviewed_sql_interpolation(node: ast.expr) -> bool:
return ( return (
isinstance(node, ast.Call) isinstance(node, ast.Call)
@ -216,7 +227,11 @@ def _is_reviewed_psql_json_lines_forwarder(
) )
def extract_runtime_query_literals(source: str) -> tuple[tuple[int, str, str], ...]: def extract_runtime_query_literals(
source: str,
*,
enforce_source_pin: bool = True,
) -> tuple[tuple[int, str, str], ...]:
"""Return every statically rendered SQL statement and its capability.""" """Return every statically rendered SQL statement and its capability."""
tree = ast.parse(source) tree = ast.parse(source)
@ -319,14 +334,20 @@ def extract_runtime_query_literals(source: str) -> tuple[tuple[int, str, str], .
if not queries: if not queries:
raise QueryContractError("runtime source contains no SQL query literals") raise QueryContractError("runtime source contains no SQL query literals")
if enforce_source_pin:
_assert_exact_runtime_source(source)
return tuple(sorted(queries, key=lambda item: item[0])) return tuple(sorted(queries, key=lambda item: item[0]))
def extract_canonical_query_literals(source: str) -> tuple[tuple[int, str], ...]: def extract_canonical_query_literals(
source: str,
*,
enforce_source_pin: bool = True,
) -> tuple[tuple[int, str], ...]:
"""Return canonical SQL literals actually passed to the runtime query runners.""" """Return canonical SQL literals actually passed to the runtime query runners."""
queries: list[tuple[int, str]] = [] queries: list[tuple[int, str]] = []
for line, sql, _capability in extract_runtime_query_literals(source): for line, sql, _capability in extract_runtime_query_literals(source, enforce_source_pin=enforce_source_pin):
normalized = _normalize_sql_identifier_syntax(sql, line) normalized = _normalize_sql_identifier_syntax(sql, line)
if CANONICAL_RELATION.search(normalized): if CANONICAL_RELATION.search(normalized):
queries.append((line, normalized)) queries.append((line, normalized))
@ -564,7 +585,7 @@ def derive_runtime_read_columns(source: str) -> dict[tuple[str, str], frozenset[
"""Mechanically derive canonical relation columns from runtime SQL.""" """Mechanically derive canonical relation columns from runtime SQL."""
observed: dict[tuple[str, str], set[str]] = defaultdict(set) observed: dict[tuple[str, str], set[str]] = defaultdict(set)
for line, sql in extract_canonical_query_literals(source): for line, sql in extract_canonical_query_literals(source, enforce_source_pin=False):
aliases: dict[str, tuple[str, str]] = {} aliases: dict[str, tuple[str, str]] = {}
explicit_starts: set[int] = set() explicit_starts: set[int] = set()
for match in ALIASED_CANONICAL_RELATION.finditer(sql): for match in ALIASED_CANONICAL_RELATION.finditer(sql):
@ -600,6 +621,7 @@ def derive_runtime_read_columns(source: str) -> dict[tuple[str, str], frozenset[
joined = ", ".join(sorted(unqualified)) joined = ", ".join(sorted(unqualified))
raise QueryContractError(f"line {line}: unqualified SQL identifiers prevent exact derivation: {joined}") raise QueryContractError(f"line {line}: unqualified SQL identifiers prevent exact derivation: {joined}")
_assert_exact_runtime_source(source)
return {relation: frozenset(columns) for relation, columns in observed.items()} return {relation: frozenset(columns) for relation, columns in observed.items()}

View file

@ -672,6 +672,39 @@ def test_runtime_query_contract_rejects_a_new_unallowlisted_column() -> None:
query_contract.verify_runtime_read_columns(mutated, expected) query_contract.verify_runtime_read_columns(mutated, expected)
def test_runtime_query_contract_pins_the_exact_reviewed_runtime_source() -> None:
runtime_path = Path(__file__).resolve().parents[1] / "hermes-agent" / "leoclean-bin" / "cloudsql_memory_tool.py"
runtime_source = runtime_path.read_text(encoding="utf-8")
assert hashlib.sha256(runtime_source.encode("utf-8")).hexdigest() == query_contract.REVIEWED_RUNTIME_SOURCE_SHA256
def test_runtime_query_contract_rejects_recursive_proposal_execution_cardinality_mutation() -> None:
runtime_path = Path(__file__).resolve().parents[1] / "hermes-agent" / "leoclean-bin" / "cloudsql_memory_tool.py"
runtime_source = runtime_path.read_text(encoding="utf-8")
recursive_sql = ''' sql = """
with recursive spam as (
select 1 as n, null::jsonb as proposal
union all
select n + 1, kb_stage.stage_leoclean_proposal(
'revise_claim', 'telegram', 'receipt:1', 'reason', '{}'::jsonb
)
from spam
where n < 10
)
select proposal from spam;
"""
'''
function_start = runtime_source.index("def propose_core_change")
sql_start = runtime_source.index(' sql = f"""\n', function_start)
sql_end = runtime_source.index('"""\n rows = psql_json_lines(', sql_start) + len('"""\n')
mutated = runtime_source[:sql_start] + recursive_sql + runtime_source[sql_end:]
assert mutated != runtime_source
with pytest.raises(query_contract.QueryContractError, match="exact reviewed runtime source"):
query_contract.extract_runtime_query_literals(mutated)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"query", "query",
( (