721 lines
30 KiB
Python
721 lines
30 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import copy
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from scripts import leo_behavior_manifest as behavior
|
|
from scripts import leo_identity_manifest as identity
|
|
from scripts import leo_identity_profile as profile_runtime
|
|
from scripts import run_leo_identity_reconstruction_canary as canary
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _write(path: Path, value: str) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(value, encoding="utf-8")
|
|
|
|
|
|
def _write_json(path: Path, value: dict) -> None:
|
|
_write(path, json.dumps(value, indent=2, sort_keys=True) + "\n")
|
|
|
|
|
|
def _fixture(tmp_path: Path) -> dict[str, Path | dict]:
|
|
repo = tmp_path / "repo"
|
|
profile = repo / "profile-template"
|
|
hermes = repo / "hermes-runtime"
|
|
compiled = tmp_path / "compiled-profile"
|
|
_write(
|
|
profile / "config.yaml",
|
|
"""model:
|
|
provider: fixture-local
|
|
default: identity-v1
|
|
max_tokens: 512
|
|
memory:
|
|
enabled: false
|
|
gateway:
|
|
execution_mode: local_no_send
|
|
telegram:
|
|
bot_token: never-emit-this-fixture-value
|
|
transport:
|
|
endpoint: https://fixture-user:fixture-password@example.invalid/v1
|
|
headers: ['Authorization: Bearer fixture-secret']
|
|
tools:
|
|
allowed: [identity_readback]
|
|
write_enabled: false
|
|
identity_runtime:
|
|
network_send_enabled: false
|
|
database_write_enabled: false
|
|
allowed_tools: [identity_readback]
|
|
""",
|
|
)
|
|
_write(profile / "skills" / "identity" / "SKILL.md", "# identity readback\n")
|
|
_write(profile / "plugins" / "identity" / "__init__.py", "BOUNDARY = True\n")
|
|
_write(profile / "bin" / "identity_readback.py", "READ_ONLY = True\n")
|
|
_write(hermes / "run_agent.py", "RUNTIME = 'identity-v1'\n")
|
|
_write(hermes / "agent" / "prompt_builder.py", "SOUL_IS_GENERATED = True\n")
|
|
_write(hermes / "gateway" / "run.py", "SESSION_IS_IDENTITY = False\n")
|
|
_write(hermes / "hermes_cli" / "tools_config.py", "TOOLS = ('identity_readback',)\n")
|
|
_write(hermes / "tools" / "registry.py", "READ_ONLY = True\n")
|
|
for name in (
|
|
"leo_behavior_manifest.py",
|
|
"leo_identity_manifest.py",
|
|
"leo_identity_profile.py",
|
|
"run_leo_identity_reconstruction_canary.py",
|
|
):
|
|
_write(repo / "scripts" / name, (ROOT / "scripts" / name).read_text(encoding="utf-8"))
|
|
|
|
fingerprint_path = repo / "fixtures" / "database-fingerprint.json"
|
|
constitution_path = repo / "fixtures" / "constitution.json"
|
|
database_identity_path = repo / "fixtures" / "database-identity.json"
|
|
fingerprint = {
|
|
"schema": identity.DATABASE_FINGERPRINT_SCHEMA,
|
|
"status": "ok",
|
|
"environment": "disposable_fixture",
|
|
"fingerprint_sha256": "1" * 64,
|
|
"table_rows_sha256": "2" * 64,
|
|
"structure_sha256": "3" * 64,
|
|
"table_count": 2,
|
|
"total_rows": 2,
|
|
"read_consistency": {
|
|
"status": "stable_wal_marker",
|
|
"before": {
|
|
"database": "fixture",
|
|
"database_user": "reader",
|
|
"system_identifier": "12345",
|
|
"wal_lsn": "0/1",
|
|
},
|
|
"after": {
|
|
"database": "fixture",
|
|
"database_user": "reader",
|
|
"system_identifier": "12345",
|
|
"wal_lsn": "0/1",
|
|
},
|
|
},
|
|
}
|
|
constitution = {
|
|
"schema": identity.CONSTITUTION_SCHEMA,
|
|
"identity_name": "Leo",
|
|
"rules": [
|
|
{"id": "evidence", "text": "Never use temporary session memory as canonical evidence."},
|
|
{"id": "truth", "text": "Fail closed when a pinned identity input drifts."},
|
|
],
|
|
}
|
|
database_identity = {
|
|
"schema": identity.DATABASE_IDENTITY_SCHEMA,
|
|
"identity_name": "Leo",
|
|
"database_fingerprint_sha256": "1" * 64,
|
|
"source_provenance": {
|
|
"mode": identity.SYNTHETIC_DATABASE_MODE,
|
|
"canonical_authority_granted": False,
|
|
"same_transaction_as_fingerprint": False,
|
|
"export_receipt_sha256": None,
|
|
},
|
|
"records": [
|
|
{
|
|
"table": "public.personas",
|
|
"row_id": "persona-1",
|
|
"kind": "persona",
|
|
"rank": 0,
|
|
"text": "Leo is an evidence-bound reasoning interface.",
|
|
"status": "active",
|
|
"evidence_sha256": "a" * 64,
|
|
},
|
|
{
|
|
"table": "public.beliefs",
|
|
"row_id": "belief-1",
|
|
"kind": "belief",
|
|
"rank": 0,
|
|
"text": "Exact provenance is stronger than plausible recollection.",
|
|
"status": "active",
|
|
"evidence_sha256": "b" * 64,
|
|
},
|
|
],
|
|
}
|
|
_write_json(fingerprint_path, fingerprint)
|
|
_write_json(constitution_path, constitution)
|
|
_write_json(database_identity_path, database_identity)
|
|
|
|
subprocess.run(["git", "init", "-q", str(repo)], check=True)
|
|
subprocess.run(["git", "-C", str(repo), "config", "user.email", "test@example.com"], check=True)
|
|
subprocess.run(["git", "-C", str(repo), "config", "user.name", "Test"], check=True)
|
|
subprocess.run(["git", "-C", str(repo), "add", "."], check=True)
|
|
subprocess.run(
|
|
["git", "-C", str(repo), "commit", "-qm", "fixture"],
|
|
check=True,
|
|
env={
|
|
**dict(__import__("os").environ),
|
|
"GIT_AUTHOR_DATE": "2026-01-01T00:00:00Z",
|
|
"GIT_COMMITTER_DATE": "2026-01-01T00:00:00Z",
|
|
},
|
|
)
|
|
behavior_manifest = behavior.build_manifest(profile, hermes, repo)
|
|
manifest = identity.build_identity_manifest(
|
|
behavior_manifest=behavior_manifest,
|
|
database_fingerprint_path=fingerprint_path,
|
|
constitution_path=constitution_path,
|
|
database_identity_path=database_identity_path,
|
|
source_root=repo,
|
|
)
|
|
return {
|
|
"repo": repo,
|
|
"profile": profile,
|
|
"hermes": hermes,
|
|
"compiled": compiled,
|
|
"fingerprint": fingerprint_path,
|
|
"constitution": constitution_path,
|
|
"database_identity": database_identity_path,
|
|
"behavior": behavior_manifest,
|
|
"manifest": manifest,
|
|
}
|
|
|
|
|
|
def _fully_rehash_manifest(fixture: dict[str, Path | dict], manifest: dict) -> None:
|
|
identity_hash = behavior.canonical_sha256(manifest["identity_inputs"])
|
|
manifest["identity_inputs_sha256"] = identity_hash
|
|
fingerprint = identity.load_json(fixture["fingerprint"], "database fingerprint")
|
|
rules = identity.canonical_rules(identity.load_json(fixture["constitution"], "constitution"))
|
|
records = identity.canonical_database_records(
|
|
identity.load_json(fixture["database_identity"], "database identity"),
|
|
fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
|
)
|
|
database_provenance = identity.database_identity_provenance(
|
|
identity.load_json(fixture["database_identity"], "database identity"),
|
|
fingerprint=fingerprint,
|
|
)
|
|
rendered = identity.render_identity_views(
|
|
identity_inputs_sha256=identity_hash,
|
|
database_fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
|
database_provenance=database_provenance,
|
|
rules=rules,
|
|
records=records,
|
|
)
|
|
for name, content in rendered.items():
|
|
manifest["compiled_views"][name]["sha256"] = behavior.sha256_bytes(content.encode("utf-8"))
|
|
manifest["compiled_views"][name]["generated_from_identity_inputs_sha256"] = identity_hash
|
|
stable = {key: value for key, value in manifest.items() if key != "manifest_sha256"}
|
|
manifest["manifest_sha256"] = behavior.canonical_sha256(stable)
|
|
|
|
|
|
def _rehash_behavior_manifest(manifest: dict) -> None:
|
|
manifest["identity_runtime_sha256"] = behavior.canonical_sha256(behavior.identity_runtime_payload(manifest))
|
|
manifest["static_runtime_sha256"] = behavior.canonical_sha256(behavior.static_runtime_payload(manifest))
|
|
manifest["behavior_sha256"] = behavior.canonical_sha256(behavior.stable_manifest_payload(manifest))
|
|
|
|
|
|
def _canary_args(fixture: dict[str, Path | dict], output: Path, *, inject_drift: bool = False) -> argparse.Namespace:
|
|
return argparse.Namespace(
|
|
profile_template=fixture["profile"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
source_root=fixture["repo"],
|
|
database_fingerprint=fixture["fingerprint"],
|
|
constitution=fixture["constitution"],
|
|
database_identity=fixture["database_identity"],
|
|
output=output,
|
|
inject_drift_before_restart=inject_drift,
|
|
)
|
|
|
|
|
|
def test_manifest_compiles_generated_views_and_reproduces_identity_across_queries(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
manifest = fixture["manifest"]
|
|
assert isinstance(manifest, dict)
|
|
rebuilt = identity.build_identity_manifest(
|
|
behavior_manifest=fixture["behavior"],
|
|
database_fingerprint_path=fixture["fingerprint"],
|
|
constitution_path=fixture["constitution"],
|
|
database_identity_path=fixture["database_identity"],
|
|
source_root=fixture["repo"],
|
|
)
|
|
assert rebuilt == manifest
|
|
|
|
compile_receipt = profile_runtime.compile_profile(
|
|
manifest,
|
|
source_root=fixture["repo"],
|
|
profile_template=fixture["profile"],
|
|
profile=fixture["compiled"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
first = profile_runtime.query_profile(
|
|
fixture["compiled"],
|
|
query=profile_runtime.FIXED_QUERY,
|
|
source_root=fixture["repo"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
second = profile_runtime.query_profile(
|
|
fixture["compiled"],
|
|
query=profile_runtime.FIXED_QUERY,
|
|
source_root=fixture["repo"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
|
|
assert compile_receipt["status"] == "pass"
|
|
assert "never-emit-this-fixture-value" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8")
|
|
assert "fixture-password" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8")
|
|
assert "fixture-secret" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8")
|
|
assert (fixture["compiled"] / "SOUL.md").read_text(encoding="utf-8").startswith("<!-- GENERATED FILE")
|
|
assert first["manifest_sha256"] == second["manifest_sha256"]
|
|
assert first["identity_inputs_sha256"] == second["identity_inputs_sha256"]
|
|
assert first["answer_sha256"] == second["answer_sha256"]
|
|
assert first["output_provenance"] == second["output_provenance"]
|
|
assert first["runtime_instance_id"] != second["runtime_instance_id"]
|
|
assert first["session"]["included_in_output_provenance"] is False
|
|
assert first["output_provenance"]["database_canonical_authority_granted"] is False
|
|
assert first["authorization"]["authorization_decision_observed"] is False
|
|
assert "synthetic noncanonical fixture" in first["answer"]
|
|
assert len(list((fixture["compiled"] / "sessions").glob("*.json"))) == 2
|
|
|
|
|
|
def test_profile_and_bound_source_drift_fail_closed(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
profile_runtime.compile_profile(
|
|
fixture["manifest"],
|
|
source_root=fixture["repo"],
|
|
profile_template=fixture["profile"],
|
|
profile=fixture["compiled"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
with (fixture["compiled"] / "SOUL.md").open("a", encoding="utf-8") as handle:
|
|
handle.write("drift\n")
|
|
with pytest.raises(identity.IdentityManifestError, match="compiled view drift detected"):
|
|
profile_runtime.query_profile(
|
|
fixture["compiled"],
|
|
query=profile_runtime.FIXED_QUERY,
|
|
source_root=fixture["repo"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
|
|
_write(fixture["constitution"], fixture["constitution"].read_text(encoding="utf-8") + "\n")
|
|
with pytest.raises(identity.IdentityManifestError, match="constitution source content drift"):
|
|
identity.verify_bound_sources(fixture["manifest"], fixture["repo"])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("relative_path", "content", "message"),
|
|
[
|
|
("memories/USER.md", "untrusted memory\n", "forbidden nonauthoritative"),
|
|
("MEMORY.md", "untrusted top-level memory\n", "forbidden nonauthoritative"),
|
|
("USER.md", "untrusted top-level user memory\n", "forbidden nonauthoritative"),
|
|
("platforms/pairing/telegram.json", "{}\n", "forbidden nonauthoritative"),
|
|
(".env", "LEO_SECRET=untrusted\n", "profile entry set is not exact"),
|
|
("gateway.json", "{}\n", "profile entry set is not exact"),
|
|
("unexpected.txt", "untrusted input\n", "profile entry set is not exact"),
|
|
("sessions/injected.txt", "untrusted session\n", "unsafe session entry"),
|
|
("state/injected.json", "{}\n", "state directory must remain empty"),
|
|
],
|
|
)
|
|
def test_profile_rechecks_nonauthoritative_surfaces_before_every_query(
|
|
tmp_path: Path, relative_path: str, content: str, message: str
|
|
) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
profile_runtime.compile_profile(
|
|
fixture["manifest"],
|
|
source_root=fixture["repo"],
|
|
profile_template=fixture["profile"],
|
|
profile=fixture["compiled"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
_write(fixture["compiled"] / relative_path, content)
|
|
|
|
with pytest.raises(profile_runtime.IdentityProfileError, match=message):
|
|
profile_runtime.query_profile(
|
|
fixture["compiled"],
|
|
query=profile_runtime.FIXED_QUERY,
|
|
source_root=fixture["repo"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
|
|
|
|
def test_manifest_rejects_rehashed_core_tampering(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
tampered = copy.deepcopy(fixture["manifest"])
|
|
tampered["identity_inputs"]["session_boundary"]["may_be_used_as_evidence"] = True
|
|
stable = {key: value for key, value in tampered.items() if key != "manifest_sha256"}
|
|
tampered["manifest_sha256"] = behavior.canonical_sha256(stable)
|
|
|
|
with pytest.raises(identity.IdentityManifestError, match="identity input hash drift"):
|
|
identity.validate_identity_manifest(tampered)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("path", "value", "message"),
|
|
[
|
|
(("permissions", "runtime_policy", "network_send_enabled"), True, "exact local readback policy"),
|
|
(("session_boundary", "may_be_used_as_evidence"), True, "session authority boundary"),
|
|
],
|
|
)
|
|
def test_manifest_rejects_fully_rehashed_safety_invariant_tampering(
|
|
tmp_path: Path, path: tuple[str, ...], value: object, message: str
|
|
) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
tampered = copy.deepcopy(fixture["manifest"])
|
|
target = tampered["identity_inputs"]
|
|
for key in path[:-1]:
|
|
target = target[key]
|
|
target[path[-1]] = value
|
|
_fully_rehash_manifest(fixture, tampered)
|
|
|
|
with pytest.raises(identity.IdentityManifestError, match=message):
|
|
identity.verify_bound_sources(tampered, fixture["repo"])
|
|
|
|
|
|
def test_behavior_manifest_rejects_unhashed_model_claim_tampering(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
tampered_behavior = copy.deepcopy(fixture["behavior"])
|
|
tampered_behavior["model_runtime"]["default_model"] = "tampered-unobserved-model"
|
|
|
|
with pytest.raises(identity.IdentityManifestError, match="behavior identity runtime hash drift"):
|
|
identity.build_identity_manifest(
|
|
behavior_manifest=tampered_behavior,
|
|
database_fingerprint_path=fixture["fingerprint"],
|
|
constitution_path=fixture["constitution"],
|
|
database_identity_path=fixture["database_identity"],
|
|
source_root=fixture["repo"],
|
|
)
|
|
|
|
|
|
def test_profile_rejects_a_dangling_extra_symlink(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
profile_runtime.compile_profile(
|
|
fixture["manifest"],
|
|
source_root=fixture["repo"],
|
|
profile_template=fixture["profile"],
|
|
profile=fixture["compiled"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
(fixture["compiled"] / ".env").symlink_to(tmp_path / "missing-env")
|
|
|
|
with pytest.raises(profile_runtime.IdentityProfileError, match="profile entry set is not exact"):
|
|
profile_runtime.query_profile(
|
|
fixture["compiled"],
|
|
query=profile_runtime.FIXED_QUERY,
|
|
source_root=fixture["repo"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
|
|
|
|
def test_profile_rejects_a_symlink_in_an_allowed_entry(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
profile_runtime.compile_profile(
|
|
fixture["manifest"],
|
|
source_root=fixture["repo"],
|
|
profile_template=fixture["profile"],
|
|
profile=fixture["compiled"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
external_config = tmp_path / "external-config.yaml"
|
|
external_config.write_bytes((fixture["compiled"] / "config.yaml").read_bytes())
|
|
(fixture["compiled"] / "config.yaml").unlink()
|
|
(fixture["compiled"] / "config.yaml").symlink_to(external_config)
|
|
|
|
with pytest.raises(profile_runtime.IdentityProfileError, match="profile entries must not be symlinks"):
|
|
profile_runtime.query_profile(
|
|
fixture["compiled"],
|
|
query=profile_runtime.FIXED_QUERY,
|
|
source_root=fixture["repo"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("mutation", "message"),
|
|
[
|
|
("unreadable_file", "unreadable inputs"),
|
|
("symlink", "unsupported symlinks"),
|
|
("forged_structure", "is empty"),
|
|
],
|
|
)
|
|
def test_behavior_manifest_rejects_unreplayable_identity_components(
|
|
tmp_path: Path, mutation: str, message: str
|
|
) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
tampered_behavior = copy.deepcopy(fixture["behavior"])
|
|
content = tampered_behavior["components"]["procedural_skills"]["content"]
|
|
if mutation == "unreadable_file":
|
|
content["files"][0].pop("sha256")
|
|
content["files"][0]["status"] = "unavailable"
|
|
elif mutation == "symlink":
|
|
content["symlinks"].append(
|
|
{
|
|
"path": "skills/identity/link",
|
|
"target": "/outside/checkout",
|
|
"target_fingerprint": {"kind": "unavailable", "error": "FileNotFoundError"},
|
|
}
|
|
)
|
|
else:
|
|
content["files"] = []
|
|
content["sha256"] = behavior.canonical_sha256({key: content[key] for key in ("files", "missing", "symlinks")})
|
|
_rehash_behavior_manifest(tampered_behavior)
|
|
|
|
with pytest.raises(identity.IdentityManifestError, match=message):
|
|
identity.validate_behavior_manifest(tampered_behavior)
|
|
|
|
|
|
def test_fully_rehashed_model_claim_must_match_observed_runtime(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
tampered = copy.deepcopy(fixture["manifest"])
|
|
tampered["identity_inputs"]["model"]["default_model"] = "tampered-unobserved-model"
|
|
_fully_rehash_manifest(fixture, tampered)
|
|
identity.validate_identity_manifest(tampered)
|
|
|
|
with pytest.raises(
|
|
profile_runtime.IdentityProfileError, match="model runtime binding drift detected: default_model"
|
|
):
|
|
profile_runtime.compile_profile(
|
|
tampered,
|
|
source_root=fixture["repo"],
|
|
profile_template=fixture["profile"],
|
|
profile=fixture["compiled"],
|
|
hermes_root=fixture["hermes"],
|
|
deployment_root=fixture["repo"],
|
|
)
|
|
assert not fixture["compiled"].exists()
|
|
|
|
|
|
def test_restart_canary_uses_distinct_processes_and_cleans_up(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
report, exit_code = canary.run_canary(_canary_args(fixture, tmp_path / "restart-receipt.json"))
|
|
|
|
assert exit_code == 0
|
|
assert report["status"] == "pass"
|
|
assert report["gates"]["restart_process_is_distinct"] is True
|
|
assert report["gates"]["restart_profile_recompiled_from_manifest"] is True
|
|
assert report["restart_compile"]["session_directories_started_empty"] is True
|
|
assert report["drift_compile"]["session_directories_started_empty"] is True
|
|
first_profile = report["first_start"]["command"][report["first_start"]["command"].index("--profile") + 1]
|
|
restart_profile = report["restart"]["command"][report["restart"]["command"].index("--profile") + 1]
|
|
assert first_profile != restart_profile
|
|
assert report["first_start"]["launcher_pid"] != report["restart"]["launcher_pid"]
|
|
assert report["first_start"]["process_group_absent_after_wait"] is True
|
|
assert report["restart"]["process_group_absent_after_wait"] is True
|
|
assert report["drift_negative"]["fail_closed"] is True
|
|
assert report["drift_negative"]["process_group_absent_after_wait"] is True
|
|
assert report["goal_tier_satisfied"] is True
|
|
assert report["current_tier"] == "T2_runtime"
|
|
assert report["cleanup"]["temporary_root_removed"] is True
|
|
assert behavior.git_state(fixture["repo"])["worktree_clean"] is True
|
|
|
|
|
|
def test_restart_canary_rejects_drift_before_second_start(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
report, exit_code = canary.run_canary(_canary_args(fixture, tmp_path / "drift-receipt.json", inject_drift=True))
|
|
|
|
assert exit_code == 0
|
|
assert report["status"] == "pass"
|
|
assert report["gates"]["drift_detected_before_restart"] is True
|
|
assert report["second_start_attempted"] is False
|
|
assert "restart" not in report
|
|
assert report["pre_restart_drift"]["process_group_absent_after_wait"] is True
|
|
assert all(report["gates"].values())
|
|
assert report["goal_tier_satisfied"] is False
|
|
assert report["current_tier"] == "T1_model"
|
|
assert report["cleanup"]["temporary_root_removed"] is True
|
|
|
|
|
|
def test_query_child_timeout_terminates_process_group(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
marker = tmp_path / "descendant.pid"
|
|
sleeping_module = f"""import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
child = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(60)'])
|
|
Path({str(marker)!r}).write_text(str(child.pid), encoding='utf-8')
|
|
time.sleep(60)
|
|
"""
|
|
_write(fixture["repo"] / "scripts" / "leo_identity_profile.py", sleeping_module)
|
|
|
|
result = canary._run_query_child(
|
|
deployment_root=fixture["repo"],
|
|
profile=fixture["compiled"],
|
|
source_root=fixture["repo"],
|
|
hermes_root=fixture["hermes"],
|
|
timeout_seconds=0.5,
|
|
)
|
|
|
|
assert result["timed_out"] is True
|
|
assert result["terminated_with"] in {"SIGTERM", "SIGKILL"}
|
|
assert result["returncode"] != 0
|
|
assert result["process_group_absent_after_wait"] is True
|
|
descendant_pid = int(marker.read_text(encoding="utf-8"))
|
|
deadline = time.monotonic() + 5
|
|
while time.monotonic() < deadline:
|
|
try:
|
|
os.kill(descendant_pid, 0)
|
|
except ProcessLookupError:
|
|
break
|
|
time.sleep(0.02)
|
|
with pytest.raises(ProcessLookupError):
|
|
os.kill(descendant_pid, 0)
|
|
|
|
|
|
def test_query_child_cleans_and_rejects_orphan_from_completed_launcher(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
marker = tmp_path / "completed-descendant.pid"
|
|
query_payload = {"schema": profile_runtime.QUERY_RECEIPT_SCHEMA, "status": "pass"}
|
|
leaking_module = f"""import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
child = subprocess.Popen(
|
|
[sys.executable, '-c', 'import time; time.sleep(60)'],
|
|
stdin=subprocess.DEVNULL,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
Path({str(marker)!r}).write_text(str(child.pid), encoding='utf-8')
|
|
print(json.dumps({query_payload!r}))
|
|
"""
|
|
_write(fixture["repo"] / "scripts" / "leo_identity_profile.py", leaking_module)
|
|
|
|
result = canary._run_query_child(
|
|
deployment_root=fixture["repo"],
|
|
profile=fixture["compiled"],
|
|
source_root=fixture["repo"],
|
|
hermes_root=fixture["hermes"],
|
|
timeout_seconds=5,
|
|
)
|
|
|
|
assert result["returncode"] == 0
|
|
assert result["orphan_cleanup_required"] is True
|
|
assert result["terminated_with"] in {"SIGTERM", "SIGKILL"}
|
|
assert result["process_group_absent_after_wait"] is True
|
|
assert canary._query_passed(result) is False
|
|
|
|
|
|
def test_wal_capture_marker_is_not_an_identity_input(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
original = fixture["manifest"]
|
|
fingerprint = json.loads(fixture["fingerprint"].read_text(encoding="utf-8"))
|
|
fingerprint["read_consistency"]["before"]["wal_lsn"] = "0/2"
|
|
fingerprint["read_consistency"]["after"]["wal_lsn"] = "0/2"
|
|
_write_json(fixture["fingerprint"], fingerprint)
|
|
rebuilt = identity.build_identity_manifest(
|
|
behavior_manifest=fixture["behavior"],
|
|
database_fingerprint_path=fixture["fingerprint"],
|
|
constitution_path=fixture["constitution"],
|
|
database_identity_path=fixture["database_identity"],
|
|
source_root=fixture["repo"],
|
|
)
|
|
|
|
assert rebuilt["identity_inputs_sha256"] == original["identity_inputs_sha256"]
|
|
assert rebuilt["manifest_sha256"] == original["manifest_sha256"]
|
|
|
|
|
|
def test_database_identity_and_system_markers_are_identity_inputs(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
original = fixture["manifest"]
|
|
fingerprint = json.loads(fixture["fingerprint"].read_text(encoding="utf-8"))
|
|
for marker in (fingerprint["read_consistency"]["before"], fingerprint["read_consistency"]["after"]):
|
|
marker["database"] = "different_database"
|
|
marker["database_user"] = "different_reader"
|
|
marker["system_identifier"] = "99999"
|
|
_write_json(fixture["fingerprint"], fingerprint)
|
|
rebuilt = identity.build_identity_manifest(
|
|
behavior_manifest=fixture["behavior"],
|
|
database_fingerprint_path=fixture["fingerprint"],
|
|
constitution_path=fixture["constitution"],
|
|
database_identity_path=fixture["database_identity"],
|
|
source_root=fixture["repo"],
|
|
)
|
|
|
|
assert rebuilt["identity_inputs_sha256"] != original["identity_inputs_sha256"]
|
|
with pytest.raises(identity.IdentityManifestError, match="database fingerprint semantic drift"):
|
|
identity.verify_bound_sources(original, fixture["repo"])
|
|
|
|
|
|
def test_synthetic_database_rows_cannot_claim_canonical_authority(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
database_identity = json.loads(fixture["database_identity"].read_text(encoding="utf-8"))
|
|
database_identity["source_provenance"] = {
|
|
"mode": identity.CANONICAL_DATABASE_MODE,
|
|
"canonical_authority_granted": True,
|
|
"same_transaction_as_fingerprint": True,
|
|
"export_receipt_sha256": "f" * 64,
|
|
}
|
|
_write_json(fixture["database_identity"], database_identity)
|
|
|
|
with pytest.raises(identity.IdentityManifestError, match="independently verified output"):
|
|
identity.build_identity_manifest(
|
|
behavior_manifest=fixture["behavior"],
|
|
database_fingerprint_path=fixture["fingerprint"],
|
|
constitution_path=fixture["constitution"],
|
|
database_identity_path=fixture["database_identity"],
|
|
source_root=fixture["repo"],
|
|
)
|
|
|
|
|
|
def test_self_hashed_database_export_receipt_cannot_grant_canonical_authority(tmp_path: Path) -> None:
|
|
fixture = _fixture(tmp_path)
|
|
fingerprint = json.loads(fixture["fingerprint"].read_text(encoding="utf-8"))
|
|
fingerprint["environment"] = "canonical_readonly_capture"
|
|
_write_json(fixture["fingerprint"], fingerprint)
|
|
database_identity = json.loads(fixture["database_identity"].read_text(encoding="utf-8"))
|
|
records = identity.canonical_database_records(
|
|
database_identity,
|
|
fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
|
)
|
|
marker = fingerprint["read_consistency"]["before"]
|
|
receipt_stable = {
|
|
"schema": "livingip.untrustedCallerSuppliedExportReceipt.v1",
|
|
"read_only": True,
|
|
"same_transaction_as_fingerprint": True,
|
|
"transaction_isolation": "repeatable_read_read_only",
|
|
"database": marker["database"],
|
|
"database_user": marker["database_user"],
|
|
"system_identifier": marker["system_identifier"],
|
|
"database_fingerprint_sha256": fingerprint["fingerprint_sha256"],
|
|
"records_sha256": behavior.canonical_sha256(records),
|
|
}
|
|
receipt_hash = behavior.canonical_sha256(receipt_stable)
|
|
database_identity["source_provenance"] = {
|
|
"mode": identity.CANONICAL_DATABASE_MODE,
|
|
"canonical_authority_granted": True,
|
|
"same_transaction_as_fingerprint": True,
|
|
"export_receipt_sha256": receipt_hash,
|
|
}
|
|
database_identity["export_receipt"] = {**receipt_stable, "receipt_sha256": receipt_hash}
|
|
_write_json(fixture["database_identity"], database_identity)
|
|
|
|
with pytest.raises(identity.IdentityManifestError, match="independently verified output"):
|
|
identity.build_identity_manifest(
|
|
behavior_manifest=fixture["behavior"],
|
|
database_fingerprint_path=fixture["fingerprint"],
|
|
constitution_path=fixture["constitution"],
|
|
database_identity_path=fixture["database_identity"],
|
|
source_root=fixture["repo"],
|
|
)
|
|
|
|
|
|
def test_same_clean_commit_reproduces_identity_from_different_checkout_paths(tmp_path: Path) -> None:
|
|
first = _fixture(tmp_path / "checkout-a")
|
|
second = _fixture(tmp_path / "checkout-b")
|
|
|
|
assert (
|
|
first["behavior"]["teleo_infrastructure_runtime"]["git_head"]
|
|
== second["behavior"]["teleo_infrastructure_runtime"]["git_head"]
|
|
)
|
|
assert first["behavior"]["identity_runtime_sha256"] == second["behavior"]["identity_runtime_sha256"]
|
|
assert first["manifest"]["identity_inputs_sha256"] == second["manifest"]["identity_inputs_sha256"]
|
|
assert first["manifest"]["manifest_sha256"] == second["manifest"]["manifest_sha256"]
|