799 lines
33 KiB
Python
799 lines
33 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from ops import verify_teleo_v3_epistemic_contract as verifier
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
MIGRATION_SQL = REPO_ROOT / "ops" / "teleo_v3_epistemic_contract.sql"
|
|
ROLLBACK_SQL = REPO_ROOT / "ops" / "rollback_teleo_v3_epistemic_contract.sql"
|
|
SECRET_STARTUP_MARKER = "secret-startup-marker"
|
|
|
|
|
|
def test_install_migration_is_additive_and_legacy_compatible() -> None:
|
|
sql = MIGRATION_SQL.read_text(encoding="utf-8").lower()
|
|
|
|
assert "add column if not exists" in sql
|
|
assert "create table if not exists public.claim_evidence_assessments" in sql
|
|
assert "admission_state text not null default 'legacy'" in sql
|
|
assert "admission_state = 'legacy'" in sql
|
|
assert "admission_state = 'admitted'" in sql
|
|
assert "accepted_by_proposal_id" in sql
|
|
assert "accepted_from_proposal_id" in sql
|
|
assert "teleo_v3_require_linked_insert" in sql
|
|
assert (
|
|
"create or replace function kb_stage.teleo_v3_require_accepted_proposal()\nreturns trigger\nlanguage plpgsql\nsecurity definer"
|
|
in sql
|
|
)
|
|
assert (
|
|
"create or replace function kb_stage.teleo_v3_enforce_apply_contract_cutover()\n"
|
|
"returns trigger\nlanguage plpgsql\nsecurity definer" in sql
|
|
)
|
|
assert "alter function kb_stage.teleo_v3_require_accepted_proposal() owner to kb_gate_owner" in sql
|
|
assert "revoke all on function kb_stage.teleo_v3_require_accepted_proposal()" in sql
|
|
assert "alter function kb_stage.teleo_v3_enforce_apply_contract_cutover() owner to kb_gate_owner" in sql
|
|
assert "revoke all on function kb_stage.teleo_v3_enforce_apply_contract_cutover()" in sql
|
|
assert "from public, kb_apply, kb_review" in sql
|
|
assert "security definer trigger invariants failed" in sql
|
|
assert "kb_gate_owner must be nologin noinherit and unprivileged" in sql
|
|
for attribute in (
|
|
"not role.rolcanlogin",
|
|
"not role.rolinherit",
|
|
"not role.rolsuper",
|
|
"not role.rolcreatedb",
|
|
"not role.rolcreaterole",
|
|
"not role.rolreplication",
|
|
"not role.rolbypassrls",
|
|
):
|
|
assert attribute in sql
|
|
assert "teleo_v3_reject_delete" in sql
|
|
assert "teleo_v3_proposal_apply_receipt_check" in sql
|
|
assert "create or replace trigger teleo_v3_claim_immutable" in sql
|
|
assert "create or replace trigger teleo_v3_guard_linked_proposal_decision" in sql
|
|
assert "'status', 'admission_state'" in sql
|
|
assert "'updated_at', 'accepted_by_proposal_id'" in sql
|
|
assert "legacy is reserved for rows present at migration" in sql
|
|
assert "cutover order: deploy the cutover-aware writer" in sql
|
|
assert "alter role kb_apply nologin" in sql
|
|
assert "active kb_apply sessions must exit" in sql
|
|
assert "retains % pending/approved v2 proposal(s) for explicit migration or rejection" in sql
|
|
assert "teleo_v3_00_enforce_apply_contract_cutover" in sql
|
|
assert "teleo_v3_enforce_apply_contract_cutover" in sql
|
|
assert sql.count("create or replace trigger teleo_v3_") == 24
|
|
assert re.search(r"(?m)^\s*create trigger teleo_v3_", sql) is None
|
|
assert "actual.tgenabled = 'o'" in sql
|
|
assert "actual.tgtype = expected.trigger_type" in sql
|
|
assert "actual.update_columns = expected.update_columns" in sql
|
|
assert "actual.when_expression is not distinct from expected.when_expression" in sql
|
|
assert "actual.argument_hex = expected.argument_hex" in sql
|
|
assert "actual.tgconstraint = 0::pg_catalog.oid" in sql
|
|
assert "actual.tgoldtable is null" in sql
|
|
assert "actual.tgnewtable is null" in sql
|
|
assert "actual.tgfoid" in sql
|
|
assert "actual.function_security_definer = expected.function_security_definer" in sql
|
|
assert "actual.function_source_sha256 = expected.function_source_sha256" in sql
|
|
assert "acl.grantee <> procedure.proowner" in sql
|
|
assert "protected role membership drift must be removed" in sql
|
|
assert "teleo_v3_contract_postflight_query_begin" in sql
|
|
assert "alter function %i.%i() owner to kb_gate_owner" in sql
|
|
assert "jsonb_typeof(new.payload->'apply_payload'->'contract_version') = 'number'" in sql
|
|
assert "migrate or reject it" in sql
|
|
assert "for share" in sql
|
|
assert "source_content_hash" in sql
|
|
assert "locator_json" in sql
|
|
assert "relation_type in ('supports', 'challenges', 'depends_on', 'contradicts')" in sql
|
|
assert "preexisting v3 schema objects lack installation ownership" in sql
|
|
assert "create table if not exists kb_stage.teleo_v3_installation_ownership" in sql
|
|
assert "installation_token = 'collision-free-install-v1'" in sql
|
|
assert "alter table kb_stage.teleo_v3_installation_ownership owner to kb_gate_owner" in sql
|
|
|
|
destructive_patterns = (
|
|
r"\bdrop\s+",
|
|
r"\btruncate\b",
|
|
r"\bdelete\s+from\b",
|
|
r"\bupdate\s+public\.",
|
|
r"\balter\s+type\b",
|
|
)
|
|
for pattern in destructive_patterns:
|
|
assert re.search(pattern, sql) is None, pattern
|
|
|
|
|
|
def test_rollback_refuses_silent_v3_data_loss_before_dropping_contract() -> None:
|
|
sql = ROLLBACK_SQL.read_text(encoding="utf-8").lower()
|
|
|
|
guard_position = sql.index("teleo_v3 rollback refused")
|
|
first_drop_position = sql.index("drop trigger")
|
|
assert guard_position < first_drop_position
|
|
assert "claim_evidence_assessments contains v3 data" in sql
|
|
assert "claims contains admitted or proposal-linked v3 data" in sql
|
|
assert "drop column if exists proposition" in sql
|
|
assert "drop function if exists kb_stage.teleo_v3_require_accepted_proposal" in sql
|
|
assert "drop function if exists kb_stage.teleo_v3_require_linked_insert" in sql
|
|
assert "drop function if exists kb_stage.teleo_v3_reject_delete" in sql
|
|
assert "drop function if exists kb_stage.teleo_v3_acquire_protected_relation_locks" in sql
|
|
assert "drop function if exists kb_stage.teleo_v3_enforce_apply_contract_cutover" in sql
|
|
assert "drop constraint if exists teleo_v3_proposal_apply_receipt_check" in sql
|
|
assert "installation ownership ledger is not exact" in sql
|
|
assert "v3-named objects lack exact installation ownership" in sql
|
|
assert "drop table if exists kb_stage.teleo_v3_installation_ownership" in sql
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("logs", "probe_returncode", "probe_stdout", "expected"),
|
|
(
|
|
(verifier.POSTGRES_READY_LOG_LINE, 0, "1\n", False),
|
|
(f"{verifier.POSTGRES_READY_LOG_LINE}\n{verifier.POSTGRES_READY_LOG_LINE}", 2, "", False),
|
|
(f"{verifier.POSTGRES_READY_LOG_LINE}\n{verifier.POSTGRES_READY_LOG_LINE}", 0, "", False),
|
|
(f"{verifier.POSTGRES_READY_LOG_LINE}\n{verifier.POSTGRES_READY_LOG_LINE}", 0, "1\n", True),
|
|
),
|
|
)
|
|
def test_postgres_final_startup_requires_second_transition_and_target_database(
|
|
logs: str,
|
|
probe_returncode: int,
|
|
probe_stdout: str,
|
|
expected: bool,
|
|
) -> None:
|
|
probe = subprocess.CompletedProcess(["psql"], probe_returncode, stdout=probe_stdout, stderr="")
|
|
|
|
assert verifier.postgres_final_startup_ready(logs, probe) is expected
|
|
|
|
|
|
def _startup_test_postgres() -> verifier.DisposablePostgres:
|
|
postgres = object.__new__(verifier.DisposablePostgres)
|
|
postgres.docker = "docker"
|
|
postgres.name = "teleo-v3-contract-test"
|
|
postgres.password = SECRET_STARTUP_MARKER
|
|
postgres.started = True
|
|
postgres.cleanup_required = True
|
|
postgres.environment = {}
|
|
return postgres
|
|
|
|
|
|
@pytest.mark.parametrize("as_bytes", (False, True))
|
|
def test_startup_detail_redacts_secret_before_tail_limit(as_bytes: bool) -> None:
|
|
postgres = _startup_test_postgres()
|
|
postgres.password = "K8VN3PQ7RM2TX6ZC9WB4HJ5DF1GS0LAE8YU3IO2N"
|
|
context = "context=database-unavailable|"
|
|
trailer = "|stage=target_database_probe|kind=timeout"
|
|
suffix_length = 500 - len(context) - len("<redacted>")
|
|
suffix = ("x" * (suffix_length - len(trailer))) + trailer
|
|
diagnostic = context + postgres.password + suffix
|
|
value: str | bytes = diagnostic.encode() if as_bytes else diagnostic
|
|
|
|
assert len(diagnostic) > 500
|
|
assert len(diagnostic) - 500 > len(context)
|
|
|
|
detail = postgres._redacted_startup_detail(value)
|
|
|
|
assert detail.startswith(f"{context}<redacted>")
|
|
assert detail.endswith(trailer)
|
|
assert postgres.password not in detail
|
|
assert all(postgres.password[offset : offset + 4] not in detail for offset in range(len(postgres.password) - 3))
|
|
|
|
|
|
def test_docker_run_timeout_is_secret_safe_and_reconciles_owned_side_effect(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
postgres = _startup_test_postgres()
|
|
postgres.password = "K8VN3PQ7RM2TX6ZC9WB4HJ5DF1GS0LAE8YU3IO2N"
|
|
postgres.started = False
|
|
postgres.cleanup_required = False
|
|
owned_container_id = "a" * 64
|
|
owned_container_exists = [True]
|
|
calls: list[tuple[list[str], dict[str, str] | None]] = []
|
|
context = "context=docker-daemon-accepted-request|"
|
|
trailer = "|stage=docker_run|kind=timeout"
|
|
suffix_length = 500 - len(context) - len("<redacted>")
|
|
timeout_stderr = context + postgres.password + ("x" * (suffix_length - len(trailer))) + trailer
|
|
|
|
def fake_run(
|
|
arguments: list[str],
|
|
*,
|
|
input_text: str | None = None,
|
|
check: bool = True,
|
|
timeout: float = 120,
|
|
environment: dict[str, str] | None = None,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
del input_text, check
|
|
calls.append((list(arguments), environment))
|
|
if arguments[0] == "run":
|
|
assert postgres.cleanup_required is True
|
|
assert postgres.started is False
|
|
assert postgres.password not in " ".join(arguments)
|
|
assert environment is not None
|
|
assert environment["POSTGRES_PASSWORD"] == postgres.password
|
|
secret_bearing_command = ["docker", *arguments, f"adjacent={postgres.password}"]
|
|
raise subprocess.TimeoutExpired(
|
|
secret_bearing_command,
|
|
timeout,
|
|
stderr=timeout_stderr,
|
|
)
|
|
if arguments[:2] == ["container", "ls"] and arguments[-1] == "{{.ID}}":
|
|
stdout = f"{owned_container_id}\n" if owned_container_exists[0] else ""
|
|
return subprocess.CompletedProcess(arguments, 0, stdout=stdout, stderr="")
|
|
if arguments[:2] == ["rm", "--force"]:
|
|
assert arguments == ["rm", "--force", owned_container_id]
|
|
owned_container_exists[0] = False
|
|
return subprocess.CompletedProcess(arguments, 0, stdout=owned_container_id, stderr="")
|
|
if arguments[0] == "inspect":
|
|
return subprocess.CompletedProcess(
|
|
arguments,
|
|
0 if owned_container_exists[0] else 1,
|
|
stdout="[]" if owned_container_exists[0] else "",
|
|
stderr="" if owned_container_exists[0] else "not found",
|
|
)
|
|
if arguments[:2] == ["container", "ls"] and arguments[-1] == "{{.Names}}":
|
|
stdout = f"{postgres.name}\n" if owned_container_exists[0] else ""
|
|
return subprocess.CompletedProcess(arguments, 0, stdout=stdout, stderr="")
|
|
raise AssertionError(f"unexpected docker arguments: {arguments}")
|
|
|
|
monkeypatch.setattr(postgres, "run", fake_run)
|
|
monkeypatch.setattr(verifier.time, "sleep", lambda _seconds: None)
|
|
|
|
with pytest.raises(verifier.VerificationError) as exc_info:
|
|
postgres.start()
|
|
cleanup = postgres.cleanup()
|
|
|
|
error = str(exc_info.value)
|
|
assert "launch state is ambiguous; cleanup_required=true" in error
|
|
assert "stage=docker_run kind=timeout timeout_seconds=120" in error
|
|
assert error.endswith(trailer)
|
|
assert exc_info.value.__suppress_context__ is True
|
|
assert postgres.password not in error
|
|
assert all(postgres.password[offset : offset + 4] not in error for offset in range(len(postgres.password) - 3))
|
|
assert postgres.started is False
|
|
assert cleanup["cleanup_reconciliation_attempted"] is True
|
|
assert cleanup["remove_attempted"] is True
|
|
assert cleanup["remove_returncodes"] == [0]
|
|
assert cleanup["owned_container_ids_observed"] == [owned_container_id]
|
|
assert cleanup["container_absent"] is True
|
|
assert cleanup["name_readback_clear"] is True
|
|
assert cleanup["instance_label_readback_clear"] is True
|
|
assert cleanup["stable_absence_readback"] is True
|
|
ownership_call = next(arguments for arguments, _environment in calls if arguments[-1] == "{{.ID}}")
|
|
assert f"name=^/{postgres.name}$" in ownership_call
|
|
assert f"label={verifier.CONTAINER_LABEL}" in ownership_call
|
|
assert f"label=livingip.canary.instance={postgres.name}" in ownership_call
|
|
|
|
|
|
def test_docker_run_normal_launch_marks_cleanup_before_execution(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
postgres = _startup_test_postgres()
|
|
postgres.started = False
|
|
postgres.cleanup_required = False
|
|
container_id = "b" * 64
|
|
waited_for_startup: list[bool] = []
|
|
|
|
def fake_run(
|
|
arguments: list[str],
|
|
*,
|
|
input_text: str | None = None,
|
|
check: bool = True,
|
|
timeout: float = 120,
|
|
environment: dict[str, str] | None = None,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
del input_text, check, timeout
|
|
if arguments[0] == "run":
|
|
assert postgres.cleanup_required is True
|
|
assert postgres.started is False
|
|
assert postgres.password not in " ".join(arguments)
|
|
assert environment is not None
|
|
assert environment["POSTGRES_PASSWORD"] == postgres.password
|
|
return subprocess.CompletedProcess(arguments, 0, stdout=f"{container_id}\n", stderr="")
|
|
assert arguments == ["inspect", postgres.name]
|
|
inspected = [
|
|
{
|
|
"Config": {
|
|
"Image": verifier.POSTGRES_IMAGE,
|
|
"Labels": {
|
|
"livingip.canary": "teleo-v3-epistemic-contract",
|
|
"livingip.canary.instance": postgres.name,
|
|
},
|
|
},
|
|
"HostConfig": {
|
|
"NetworkMode": "none",
|
|
"Tmpfs": {"/var/lib/postgresql/data": "rw,nosuid,nodev,size=384m"},
|
|
},
|
|
"Image": "sha256:postgres-image",
|
|
"Mounts": [],
|
|
}
|
|
]
|
|
return subprocess.CompletedProcess(arguments, 0, stdout=json.dumps(inspected), stderr="")
|
|
|
|
def fake_wait_for_final_startup() -> None:
|
|
assert postgres.cleanup_required is True
|
|
assert postgres.started is True
|
|
waited_for_startup.append(True)
|
|
|
|
monkeypatch.setattr(postgres, "run", fake_run)
|
|
monkeypatch.setattr(postgres, "_wait_for_final_startup", fake_wait_for_final_startup)
|
|
|
|
environment = postgres.start()
|
|
|
|
assert waited_for_startup == [True]
|
|
assert postgres.started is True
|
|
assert postgres.cleanup_required is True
|
|
assert environment["container_name"] == postgres.name
|
|
assert environment["network_mode"] == "none"
|
|
assert environment["tmpfs_data_dir"] is True
|
|
assert environment["docker_volume_mounts"] == []
|
|
|
|
|
|
def test_cleanup_never_removes_foreign_exact_name_without_instance_labels(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
postgres = _startup_test_postgres()
|
|
postgres.started = False
|
|
postgres.cleanup_required = True
|
|
calls: list[list[str]] = []
|
|
|
|
def fake_run(
|
|
arguments: list[str],
|
|
*,
|
|
input_text: str | None = None,
|
|
check: bool = True,
|
|
timeout: float = 120,
|
|
environment: dict[str, str] | None = None,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
del input_text, check, timeout, environment
|
|
calls.append(list(arguments))
|
|
if arguments[:2] == ["container", "ls"] and arguments[-1] == "{{.ID}}":
|
|
return subprocess.CompletedProcess(arguments, 0, stdout="", stderr="")
|
|
if arguments[0] == "inspect":
|
|
return subprocess.CompletedProcess(arguments, 0, stdout="[]", stderr="")
|
|
if arguments[:2] == ["container", "ls"] and arguments[-1] == "{{.Names}}":
|
|
has_instance_filter = f"label=livingip.canary.instance={postgres.name}" in arguments
|
|
stdout = "" if has_instance_filter else f"{postgres.name}\n"
|
|
return subprocess.CompletedProcess(arguments, 0, stdout=stdout, stderr="")
|
|
raise AssertionError(f"unexpected docker arguments: {arguments}")
|
|
|
|
monkeypatch.setattr(postgres, "run", fake_run)
|
|
monkeypatch.setattr(verifier.time, "sleep", lambda _seconds: None)
|
|
|
|
cleanup = postgres.cleanup()
|
|
|
|
assert not any(arguments[:2] == ["rm", "--force"] for arguments in calls)
|
|
assert cleanup["remove_attempted"] is False
|
|
assert cleanup["foreign_name_collision_detected"] is True
|
|
assert cleanup["container_absent"] is False
|
|
assert cleanup["name_readback_clear"] is False
|
|
assert cleanup["instance_label_readback_clear"] is True
|
|
assert cleanup["stable_absence_readback"] is False
|
|
|
|
|
|
@pytest.mark.parametrize("hung_stage", ("docker_logs", "target_database_probe"))
|
|
def test_postgres_startup_commands_are_capped_to_remaining_deadline(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
hung_stage: str,
|
|
) -> None:
|
|
postgres = _startup_test_postgres()
|
|
now = [100.0]
|
|
observed_timeouts: list[float] = []
|
|
ready_logs = f"{verifier.POSTGRES_READY_LOG_LINE}\n{verifier.POSTGRES_READY_LOG_LINE}\n"
|
|
|
|
def fake_run(
|
|
arguments: list[str],
|
|
*,
|
|
input_text: str | None = None,
|
|
check: bool = True,
|
|
timeout: float = 120,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
del input_text, check
|
|
assert arguments == ["logs", postgres.name]
|
|
if hung_stage == "docker_logs":
|
|
observed_timeouts.append(timeout)
|
|
now[0] += timeout
|
|
raise subprocess.TimeoutExpired(arguments, timeout, stderr=SECRET_STARTUP_MARKER)
|
|
return subprocess.CompletedProcess(arguments, 0, stdout=ready_logs, stderr="")
|
|
|
|
def fake_psql(
|
|
sql: str,
|
|
*,
|
|
check: bool = True,
|
|
timeout: float = 120,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
del check
|
|
assert sql == "select 1;"
|
|
observed_timeouts.append(timeout)
|
|
now[0] += timeout
|
|
raise subprocess.TimeoutExpired(["psql"], timeout, stderr=SECRET_STARTUP_MARKER)
|
|
|
|
monkeypatch.setattr(verifier.time, "monotonic", lambda: now[0])
|
|
monkeypatch.setattr(postgres, "run", fake_run)
|
|
monkeypatch.setattr(postgres, "psql", fake_psql)
|
|
|
|
with pytest.raises(verifier.VerificationError) as exc_info:
|
|
postgres._wait_for_final_startup(startup_timeout=3.0, probe_timeout=120.0, poll_interval=0)
|
|
|
|
assert observed_timeouts == [3.0]
|
|
assert f"stage={hung_stage} kind=timeout timeout_seconds=3" in str(exc_info.value)
|
|
assert SECRET_STARTUP_MARKER not in str(exc_info.value)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("failed_stage", "expected_context"),
|
|
(
|
|
("docker_logs", "stage=docker_logs kind=nonzero returncode=17 detail=daemon <redacted>"),
|
|
(
|
|
"target_database_probe",
|
|
"stage=target_database_probe kind=nonzero returncode=2 detail=database missing <redacted>",
|
|
),
|
|
),
|
|
)
|
|
def test_postgres_startup_failure_reports_sanitized_terminal_context(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
failed_stage: str,
|
|
expected_context: str,
|
|
) -> None:
|
|
postgres = _startup_test_postgres()
|
|
now = [200.0]
|
|
ready_logs = f"{verifier.POSTGRES_READY_LOG_LINE}\n{verifier.POSTGRES_READY_LOG_LINE}\n"
|
|
|
|
def fake_run(
|
|
arguments: list[str],
|
|
*,
|
|
input_text: str | None = None,
|
|
check: bool = True,
|
|
timeout: float = 120,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
del input_text, check, timeout
|
|
assert arguments == ["logs", postgres.name]
|
|
if failed_stage == "docker_logs":
|
|
return subprocess.CompletedProcess(
|
|
arguments,
|
|
17,
|
|
stdout="",
|
|
stderr=f"daemon {SECRET_STARTUP_MARKER}",
|
|
)
|
|
return subprocess.CompletedProcess(arguments, 0, stdout=ready_logs, stderr="")
|
|
|
|
def fake_psql(
|
|
sql: str,
|
|
*,
|
|
check: bool = True,
|
|
timeout: float = 120,
|
|
) -> subprocess.CompletedProcess[str]:
|
|
del check, timeout
|
|
assert sql == "select 1;"
|
|
return subprocess.CompletedProcess(
|
|
["psql"],
|
|
2,
|
|
stdout="",
|
|
stderr=f"database missing {SECRET_STARTUP_MARKER}",
|
|
)
|
|
|
|
def fake_sleep(seconds: float) -> None:
|
|
now[0] += seconds
|
|
|
|
monkeypatch.setattr(verifier.time, "monotonic", lambda: now[0])
|
|
monkeypatch.setattr(verifier.time, "sleep", fake_sleep)
|
|
monkeypatch.setattr(postgres, "run", fake_run)
|
|
monkeypatch.setattr(postgres, "psql", fake_psql)
|
|
|
|
with pytest.raises(verifier.VerificationError) as exc_info:
|
|
postgres._wait_for_final_startup(startup_timeout=0.25, probe_timeout=10.0, poll_interval=0.25)
|
|
|
|
error = str(exc_info.value)
|
|
assert expected_context in error
|
|
assert "within 0.25s after 1 attempt(s)" in error
|
|
assert SECRET_STARTUP_MARKER not in error
|
|
|
|
|
|
@pytest.mark.skipif(not verifier.docker_available(), reason="Docker daemon is required")
|
|
def test_preexisting_v3_named_objects_are_never_adopted_or_rolled_back() -> None:
|
|
postgres = verifier.DisposablePostgres()
|
|
cleanup: dict[str, object] = {}
|
|
try:
|
|
postgres.start()
|
|
postgres.psql(verifier.BOOTSTRAP_SQL)
|
|
postgres.psql(
|
|
"""
|
|
alter table public.claims
|
|
add column owner_agent_id uuid,
|
|
add column proposition text,
|
|
add column body_markdown text,
|
|
add column scope text,
|
|
add column access_scope text,
|
|
add column admission_state text,
|
|
add column revision_criteria text,
|
|
add column revision_kind text,
|
|
add column supersedes_id uuid,
|
|
add column accepted_by_proposal_id uuid;
|
|
update public.claims
|
|
set owner_agent_id = '11111111-1111-4111-8111-111111111111'::uuid,
|
|
proposition = 'claims.proposition',
|
|
body_markdown = 'claims.body_markdown',
|
|
scope = 'claims.scope',
|
|
access_scope = 'claims.access_scope',
|
|
admission_state = 'claims.admission_state',
|
|
revision_criteria = 'claims.revision_criteria',
|
|
revision_kind = 'claims.revision_kind',
|
|
supersedes_id = '20000000-0000-4000-8000-000000000002'::uuid,
|
|
accepted_by_proposal_id = '30000000-0000-4000-8000-000000000003'::uuid
|
|
where id = '20000000-0000-4000-8000-000000000001'::uuid;
|
|
|
|
alter table public.sources
|
|
add column canonical_url text,
|
|
add column storage_uri text,
|
|
add column content_hash text,
|
|
add column access_scope text,
|
|
add column ingestion_origin text,
|
|
add column provenance_status text,
|
|
add column source_class text,
|
|
add column auto_promotion_policy text,
|
|
add column accepted_by_proposal_id uuid;
|
|
update public.sources
|
|
set canonical_url = 'sources.canonical_url',
|
|
storage_uri = 'sources.storage_uri',
|
|
content_hash = 'sources.content_hash',
|
|
access_scope = 'sources.access_scope',
|
|
ingestion_origin = 'sources.ingestion_origin',
|
|
provenance_status = 'sources.provenance_status',
|
|
source_class = 'sources.source_class',
|
|
auto_promotion_policy = 'sources.auto_promotion_policy',
|
|
accepted_by_proposal_id = '30000000-0000-4000-8000-000000000003'::uuid
|
|
where id = '20000000-0000-4000-8000-000000000003'::uuid;
|
|
|
|
alter table public.claim_evidence
|
|
add column excerpt text,
|
|
add column locator_json jsonb,
|
|
add column source_content_hash text,
|
|
add column rationale text,
|
|
add column polarity text,
|
|
add column accepted_from_proposal_id uuid;
|
|
update public.claim_evidence
|
|
set excerpt = 'claim_evidence.excerpt',
|
|
locator_json = '{"sentinel":"claim_evidence.locator_json"}'::jsonb,
|
|
source_content_hash = 'claim_evidence.source_content_hash',
|
|
rationale = 'claim_evidence.rationale',
|
|
polarity = 'claim_evidence.polarity',
|
|
accepted_from_proposal_id = '30000000-0000-4000-8000-000000000003'::uuid
|
|
where id = '20000000-0000-4000-8000-000000000004'::uuid;
|
|
|
|
alter table public.claim_edges
|
|
add column relation_type text,
|
|
add column rationale text,
|
|
add column scope_or_conditions text,
|
|
add column accepted_by_proposal_id uuid;
|
|
update public.claim_edges
|
|
set relation_type = 'claim_edges.relation_type',
|
|
rationale = 'claim_edges.rationale',
|
|
scope_or_conditions = 'claim_edges.scope_or_conditions',
|
|
accepted_by_proposal_id = '30000000-0000-4000-8000-000000000003'::uuid
|
|
where id = '20000000-0000-4000-8000-000000000005'::uuid;
|
|
|
|
create table public.claim_evidence_assessments (
|
|
id uuid primary key,
|
|
legacy_marker text
|
|
);
|
|
"""
|
|
)
|
|
state_sql = """
|
|
select jsonb_build_object(
|
|
'claims', (
|
|
select to_jsonb(row_data) from (
|
|
select owner_agent_id, proposition, body_markdown, scope, access_scope, admission_state,
|
|
revision_criteria, revision_kind, supersedes_id, accepted_by_proposal_id
|
|
from public.claims
|
|
where id = '20000000-0000-4000-8000-000000000001'::uuid
|
|
) row_data
|
|
),
|
|
'sources', (
|
|
select to_jsonb(row_data) from (
|
|
select canonical_url, storage_uri, content_hash, access_scope, ingestion_origin,
|
|
provenance_status, source_class, auto_promotion_policy, accepted_by_proposal_id
|
|
from public.sources
|
|
where id = '20000000-0000-4000-8000-000000000003'::uuid
|
|
) row_data
|
|
),
|
|
'claim_evidence', (
|
|
select to_jsonb(row_data) from (
|
|
select excerpt, locator_json, source_content_hash, rationale, polarity, accepted_from_proposal_id
|
|
from public.claim_evidence
|
|
where id = '20000000-0000-4000-8000-000000000004'::uuid
|
|
) row_data
|
|
),
|
|
'claim_edges', (
|
|
select to_jsonb(row_data) from (
|
|
select relation_type, rationale, scope_or_conditions, accepted_by_proposal_id
|
|
from public.claim_edges
|
|
where id = '20000000-0000-4000-8000-000000000005'::uuid
|
|
) row_data
|
|
),
|
|
'assessment_columns', (
|
|
select jsonb_agg(column_name order by ordinal_position)
|
|
from information_schema.columns
|
|
where table_schema = 'public' and table_name = 'claim_evidence_assessments'
|
|
),
|
|
'assessment_rows', (select count(*) from public.claim_evidence_assessments),
|
|
'ownership_ledger', to_regclass('kb_stage.teleo_v3_installation_ownership')::text
|
|
)::text;
|
|
"""
|
|
before = postgres.psql_json(state_sql)
|
|
|
|
install = postgres.apply_file(MIGRATION_SQL, check=False)
|
|
rollback = postgres.apply_file(ROLLBACK_SQL, check=False)
|
|
after = postgres.psql_json(state_sql)
|
|
|
|
assert install.returncode != 0
|
|
assert "preexisting V3 schema objects lack installation ownership" in install.stderr
|
|
assert rollback.returncode != 0
|
|
assert "V3-named objects lack exact installation ownership" in rollback.stderr
|
|
assert before == after
|
|
assert set(before["claims"]) == {
|
|
"owner_agent_id",
|
|
"proposition",
|
|
"body_markdown",
|
|
"scope",
|
|
"access_scope",
|
|
"admission_state",
|
|
"revision_criteria",
|
|
"revision_kind",
|
|
"supersedes_id",
|
|
"accepted_by_proposal_id",
|
|
}
|
|
assert set(before["sources"]) == {
|
|
"canonical_url",
|
|
"storage_uri",
|
|
"content_hash",
|
|
"access_scope",
|
|
"ingestion_origin",
|
|
"provenance_status",
|
|
"source_class",
|
|
"auto_promotion_policy",
|
|
"accepted_by_proposal_id",
|
|
}
|
|
assert set(before["claim_evidence"]) == {
|
|
"excerpt",
|
|
"locator_json",
|
|
"source_content_hash",
|
|
"rationale",
|
|
"polarity",
|
|
"accepted_from_proposal_id",
|
|
}
|
|
assert set(before["claim_edges"]) == {
|
|
"relation_type",
|
|
"rationale",
|
|
"scope_or_conditions",
|
|
"accepted_by_proposal_id",
|
|
}
|
|
assert all(value is not None for value in before["claims"].values())
|
|
assert all(value is not None for value in before["sources"].values())
|
|
assert all(value is not None for value in before["claim_evidence"].values())
|
|
assert all(value is not None for value in before["claim_edges"].values())
|
|
assert before["assessment_columns"] == ["id", "legacy_marker"]
|
|
assert before["assessment_rows"] == 0
|
|
assert before["ownership_ledger"] is None
|
|
finally:
|
|
cleanup = postgres.cleanup()
|
|
|
|
assert cleanup["container_absent"] is True
|
|
assert cleanup["name_readback_clear"] is True
|
|
assert cleanup["instance_label_readback_clear"] is True
|
|
|
|
|
|
@pytest.mark.skipif(not verifier.docker_available(), reason="Docker daemon is required")
|
|
def test_partial_v3_contract_is_never_adopted_or_rolled_back() -> None:
|
|
postgres = verifier.DisposablePostgres()
|
|
cleanup: dict[str, object] = {}
|
|
try:
|
|
postgres.start()
|
|
postgres.psql(verifier.BOOTSTRAP_SQL)
|
|
postgres.psql(
|
|
"""
|
|
create function kb_stage.teleo_v3_partial_sentinel()
|
|
returns text
|
|
language sql
|
|
immutable
|
|
as $$ select 'legacy-partial-sentinel'::text $$;
|
|
"""
|
|
)
|
|
state_sql = """
|
|
select jsonb_build_object(
|
|
'sentinel_result', kb_stage.teleo_v3_partial_sentinel(),
|
|
'sentinel_definition', pg_get_functiondef('kb_stage.teleo_v3_partial_sentinel()'::regprocedure),
|
|
'ownership_ledger', to_regclass('kb_stage.teleo_v3_installation_ownership')::text
|
|
)::text;
|
|
"""
|
|
before = postgres.psql_json(state_sql)
|
|
|
|
install = postgres.apply_file(MIGRATION_SQL, check=False)
|
|
rollback = postgres.apply_file(ROLLBACK_SQL, check=False)
|
|
after = postgres.psql_json(state_sql)
|
|
|
|
assert install.returncode != 0
|
|
assert "partial or altered V3 contract lacks exact installation ownership" in install.stderr
|
|
assert rollback.returncode != 0
|
|
assert "V3-named objects lack exact installation ownership" in rollback.stderr
|
|
assert before == after
|
|
assert before["sentinel_result"] == "legacy-partial-sentinel"
|
|
assert "teleo_v3_partial_sentinel" in before["sentinel_definition"]
|
|
assert before["ownership_ledger"] is None
|
|
finally:
|
|
cleanup = postgres.cleanup()
|
|
|
|
assert cleanup["container_absent"] is True
|
|
assert cleanup["name_readback_clear"] is True
|
|
assert cleanup["instance_label_readback_clear"] is True
|
|
|
|
|
|
def test_verifier_has_no_external_database_target_surface(tmp_path: Path) -> None:
|
|
args = verifier.parse_args(["--output", str(tmp_path / "receipt.json")])
|
|
assert args.output == tmp_path / "receipt.json"
|
|
|
|
source = Path(verifier.__file__).read_text(encoding="utf-8")
|
|
assert "DATABASE_URL" not in source
|
|
assert "PGHOST" not in source
|
|
assert '"--network",\n "none"' in source
|
|
assert '"--tmpfs"' in source
|
|
assert 'production_access_attempted": False' in source
|
|
|
|
|
|
def test_failure_receipt_requires_exact_sqlstate() -> None:
|
|
completed = subprocess.CompletedProcess(
|
|
args=["psql"],
|
|
returncode=3,
|
|
stdout="",
|
|
stderr="ERROR: 23514: expected constraint failure\n",
|
|
)
|
|
receipt = verifier.failure_receipt(completed, "23514")
|
|
|
|
assert receipt["refused"] is True
|
|
assert receipt["observed_sqlstate"] == "23514"
|
|
assert receipt["expected_sqlstate_observed"] is True
|
|
|
|
|
|
def test_verifier_exercises_concurrent_proposal_downgrade_lock() -> None:
|
|
source = Path(verifier.__file__).read_text(encoding="utf-8")
|
|
|
|
assert "proposal_decision_lock_race_check" in source
|
|
assert "teleo_v3_proposal_lock_holder" in source
|
|
assert "set statement_timeout = '750ms'" in source
|
|
assert 'failure_receipt(downgrade, "57014")' in source
|
|
|
|
|
|
def test_verifier_asserts_security_definer_owner_search_path_and_acl() -> None:
|
|
source = Path(verifier.__file__).read_text(encoding="utf-8")
|
|
|
|
assert '"owner_name": "kb_gate_owner"' in source
|
|
assert '"security_definer": True' in source
|
|
assert '"config": ["search_path=pg_catalog, pg_temp"]' in source
|
|
assert '"owner_only_execute": True' in source
|
|
|
|
|
|
def test_verifier_cleanup_bypass_is_bounded_to_disposable_superuser_transaction() -> None:
|
|
source = Path(verifier.__file__).read_text(encoding="utf-8")
|
|
|
|
assert "set local session_replication_role = replica" in source
|
|
assert '"isolated-disposable-superuser-transaction"' in source
|
|
assert "Production writers must never receive" in source
|
|
|
|
|
|
@pytest.mark.skipif(not verifier.docker_available(), reason="Docker daemon is required")
|
|
def test_disposable_postgres_contract_end_to_end(tmp_path: Path) -> None:
|
|
output = tmp_path / "teleo-v3-epistemic-contract-receipt.json"
|
|
|
|
receipt = verifier.run_verification(output)
|
|
|
|
assert receipt["status"] == "pass", receipt.get("error")
|
|
assert receipt["required_tier"] == "T2_runtime"
|
|
assert receipt["current_tier"] == "T2_runtime_disposable_postgresql"
|
|
assert receipt["production_access_attempted"] is False
|
|
assert receipt["production_migration_shipped"] is False
|
|
assert receipt["environment"]["network_mode"] == "none"
|
|
assert receipt["environment"]["tmpfs_data_dir"] is True
|
|
assert receipt["environment"]["docker_volume_mounts"] == []
|
|
assert len(receipt["checks"]) >= 20
|
|
assert all(receipt["checks"].values()), receipt["checks"]
|
|
assert all(item["refused"] for item in receipt["adversarial"].values())
|
|
assert all(item["expected_sqlstate_observed"] for item in receipt["adversarial"].values())
|
|
assert receipt["rollback_with_data"]["refused"] is True
|
|
assert receipt["cleanup"]["container_absent"] is True
|
|
assert receipt["cleanup"]["instance_label_readback_clear"] is True
|
|
assert json.loads(output.read_text(encoding="utf-8")) == receipt
|