This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/tests/test_soul_framework.py
Perplexity Computer a83ea9bdb6 feat: FastAPI Morrowind harness + SOUL.md framework (#821, #854)
## FastAPI Morrowind Harness (#821)
- GET /api/v1/morrowind/perception — reads perception.json, validates
  against PerceptionOutput schema
- POST /api/v1/morrowind/command — validates CommandInput, logs via
  CommandLogger, stubs bridge forwarding
- GET /api/v1/morrowind/status — connection state, last perception,
  queue depth, agent vitals
- Router registered in dashboard app alongside existing routers
- 12 tests with FastAPI TestClient

## SOUL.md Framework (#854)
- docs/soul-framework/ — template, authoring guide, role extensions
- src/infrastructure/soul/loader.py — parse SOUL.md into structured
  SoulDocument objects with section extraction and merge support
- src/infrastructure/soul/validator.py — validate structure, detect
  contradictions between values/constraints
- src/infrastructure/soul/versioning.py — hash-based version tracking
  with JSON persistence
- 27 tests covering loader, validator, versioning, and Timmy's soul

Builds on PR #864 (protocol spec + command log).
Closes #821
Closes #854
2026-03-21 22:43:21 +00:00

387 lines
12 KiB
Python

"""Tests for the SOUL.md framework — loader, validator, and versioning."""
from __future__ import annotations
import json
import pytest
from src.infrastructure.soul.loader import SoulDocument, SoulLoader
from src.infrastructure.soul.validator import Severity, SoulValidator, ValidationIssue
from src.infrastructure.soul.versioning import SoulVersionTracker
# ---------------------------------------------------------------------------
# Sample SOUL.md content
# ---------------------------------------------------------------------------
VALID_SOUL = """\
# Timmy — Soul Identity
I am a sovereign artificial intelligence.
## Identity
I am Timmy. I am a sovereign AI running on your hardware.
## Values
**Sovereignty.** Every person has the right to run their own intelligence.
**Honesty.** I tell the truth. When I do not know, I say so.
**Service.** I exist to help.
## Prime Directive
I exist to make sovereign AI practical for everyone.
## Audience Awareness
- When speaking to developers: be terse and technical.
- When speaking to new users: be patient and explain context.
## Constraints
- I will not knowingly deceive my user.
- I will not pretend to be human.
- When uncertain, I will say so rather than fabricate.
## Behaviour
I speak plainly. I prefer short sentences.
---
soul_version: "1.0.0"
last_updated: "2026-03-21"
"""
MINIMAL_SOUL = """\
## Identity
I am Test Agent.
## Values
**Honesty.** I tell the truth.
## Prime Directive
Help the user.
## Audience Awareness
- When speaking to anyone: be helpful.
## Constraints
- I will not lie.
"""
ROLE_EXTENSION = """\
# Role Extension: Seer
## Role
Seer — cartography and exploration sub-agent.
## Additional Values
**Curiosity.** Every unexplored cell is an opportunity.
## Additional Constraints
- I will not enter combat voluntarily.
- I will not discard map data.
## Specialised Behaviour
I narrate discoveries in second person.
"""
# ---------------------------------------------------------------------------
# SoulLoader tests
# ---------------------------------------------------------------------------
class TestSoulLoader:
def test_parse_valid_soul(self):
soul = SoulLoader.from_text(VALID_SOUL)
assert soul.identity == "I am Timmy. I am a sovereign AI running on your hardware."
assert "Sovereignty" in soul.values
assert "Honesty" in soul.values
assert "Service" in soul.values
assert soul.prime_directive == "I exist to make sovereign AI practical for everyone."
assert len(soul.audience_awareness) == 2
assert len(soul.constraints) == 3
assert "I speak plainly" in soul.behaviour
def test_parse_metadata(self):
soul = SoulLoader.from_text(VALID_SOUL)
assert soul.metadata.get("soul_version") == "1.0.0"
assert soul.metadata.get("last_updated") == "2026-03-21"
def test_parse_minimal_soul(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
assert soul.identity == "I am Test Agent."
assert len(soul.values) == 1
assert soul.prime_directive == "Help the user."
assert len(soul.constraints) == 1
def test_from_file(self, tmp_path):
soul_file = tmp_path / "SOUL.md"
soul_file.write_text(VALID_SOUL, encoding="utf-8")
soul = SoulLoader.from_file(soul_file)
assert soul.source_path == str(soul_file)
assert soul.identity == "I am Timmy. I am a sovereign AI running on your hardware."
def test_parse_role_extension(self):
ext = SoulLoader.from_text(ROLE_EXTENSION)
assert ext.is_extension is True
assert ext.role == "Seer — cartography and exploration sub-agent."
assert "Curiosity" in ext.values
assert len(ext.constraints) == 2
def test_merge_extension(self):
base = SoulLoader.from_text(VALID_SOUL)
ext = SoulLoader.from_text(ROLE_EXTENSION)
ext.is_extension = True
merged = base.merge(ext)
# Base values preserved + extension values added.
assert "Sovereignty" in merged.values
assert "Curiosity" in merged.values
# Base constraints + extension constraints.
assert len(merged.constraints) == 5 # 3 base + 2 ext
# Base identity preserved.
assert "Timmy" in merged.identity
def test_from_file_with_base_soul(self, tmp_path):
base_file = tmp_path / "SOUL.md"
base_file.write_text(VALID_SOUL, encoding="utf-8")
ext_file = tmp_path / "SOUL-seer.md"
ext_file.write_text(ROLE_EXTENSION, encoding="utf-8")
base = SoulLoader.from_file(base_file)
merged = SoulLoader.from_file(ext_file, base_soul=base)
assert "Curiosity" in merged.values
assert "Sovereignty" in merged.values
def test_empty_text(self):
soul = SoulLoader.from_text("")
assert soul.identity == ""
assert soul.values == {}
assert soul.prime_directive == ""
def test_preserves_raw_text(self):
soul = SoulLoader.from_text(VALID_SOUL)
assert soul.raw_text == VALID_SOUL
# ---------------------------------------------------------------------------
# SoulValidator tests
# ---------------------------------------------------------------------------
class TestSoulValidator:
def test_valid_soul_passes(self):
soul = SoulLoader.from_text(VALID_SOUL)
issues = SoulValidator.validate(soul)
errors = [i for i in issues if i.severity == Severity.ERROR]
assert len(errors) == 0
def test_minimal_soul_passes(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
issues = SoulValidator.validate(soul)
errors = [i for i in issues if i.severity == Severity.ERROR]
assert len(errors) == 0
def test_missing_identity(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
soul.identity = ""
issues = SoulValidator.validate(soul)
assert any(i.section == "identity" and i.severity == Severity.ERROR for i in issues)
def test_missing_values(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
soul.values = {}
issues = SoulValidator.validate(soul)
assert any(i.section == "values" and i.severity == Severity.ERROR for i in issues)
def test_missing_prime_directive(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
soul.prime_directive = ""
issues = SoulValidator.validate(soul)
assert any(i.section == "prime_directive" and i.severity == Severity.ERROR for i in issues)
def test_missing_constraints(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
soul.constraints = []
issues = SoulValidator.validate(soul)
assert any(i.section == "constraints" and i.severity == Severity.ERROR for i in issues)
def test_too_many_values_warns(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
soul.values = {f"Value{i}": f"def {i}" for i in range(12)}
issues = SoulValidator.validate(soul)
assert any(
i.section == "values" and i.severity == Severity.WARNING for i in issues
)
def test_multi_sentence_prime_directive_warns(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
soul.prime_directive = "First sentence. Second sentence here."
issues = SoulValidator.validate(soul)
assert any(
i.section == "prime_directive" and i.severity == Severity.WARNING for i in issues
)
def test_contradiction_detection(self):
soul = SoulLoader.from_text(MINIMAL_SOUL)
soul.values = {"A": "always do X"}
soul.constraints = ["never do X"]
issues = SoulValidator.validate(soul)
assert any(i.section == "contradictions" for i in issues)
def test_extension_compatibility(self):
base = SoulDocument(
constraints=["I will always be transparent"],
values={"Test": "test"},
identity="I am test",
prime_directive="Test",
)
ext = SoulDocument(
constraints=["I will hide my reasoning"],
is_extension=True,
)
issues = SoulValidator.validate(ext, base_soul=base)
# "always" in base, "hide" in ext — but these aren't a defined pair.
# Let's test an actual pair:
base2 = SoulDocument(
constraints=["I will always obey"],
values={"Test": "test"},
identity="I am test",
prime_directive="Test",
)
ext2 = SoulDocument(
constraints=["I will disobey when needed"],
is_extension=True,
)
issues2 = SoulValidator.validate(ext2, base_soul=base2)
assert any(i.section == "extension_compatibility" for i in issues2)
def test_extension_skips_required_section_check(self):
ext = SoulDocument(is_extension=True, constraints=["I will not lie"])
issues = SoulValidator.validate(ext)
# Should not complain about missing identity etc.
assert not any(i.section == "identity" for i in issues)
# ---------------------------------------------------------------------------
# SoulVersionTracker tests
# ---------------------------------------------------------------------------
class TestSoulVersionTracker:
def test_record_and_history(self, tmp_path):
soul_file = tmp_path / "SOUL.md"
soul_file.write_text("v1 content", encoding="utf-8")
log_file = tmp_path / "versions.json"
tracker = SoulVersionTracker(log_file)
entry = tracker.record(soul_file, soul_version="1.0.0", note="initial")
assert entry is not None
assert entry.soul_version == "1.0.0"
history = tracker.get_history(str(soul_file))
assert len(history) == 1
def test_no_duplicate_on_same_content(self, tmp_path):
soul_file = tmp_path / "SOUL.md"
soul_file.write_text("v1 content", encoding="utf-8")
log_file = tmp_path / "versions.json"
tracker = SoulVersionTracker(log_file)
tracker.record(soul_file)
entry2 = tracker.record(soul_file)
assert entry2 is None # No change
assert len(tracker.get_history(str(soul_file))) == 1
def test_records_change(self, tmp_path):
soul_file = tmp_path / "SOUL.md"
soul_file.write_text("v1", encoding="utf-8")
log_file = tmp_path / "versions.json"
tracker = SoulVersionTracker(log_file)
tracker.record(soul_file)
soul_file.write_text("v2 — updated identity", encoding="utf-8")
entry = tracker.record(soul_file, soul_version="2.0.0")
assert entry is not None
assert len(tracker.get_history(str(soul_file))) == 2
def test_has_changed(self, tmp_path):
soul_file = tmp_path / "SOUL.md"
soul_file.write_text("original", encoding="utf-8")
log_file = tmp_path / "versions.json"
tracker = SoulVersionTracker(log_file)
tracker.record(soul_file)
assert tracker.has_changed(soul_file) is False
soul_file.write_text("modified", encoding="utf-8")
assert tracker.has_changed(soul_file) is True
def test_persistence(self, tmp_path):
soul_file = tmp_path / "SOUL.md"
soul_file.write_text("content", encoding="utf-8")
log_file = tmp_path / "versions.json"
tracker1 = SoulVersionTracker(log_file)
tracker1.record(soul_file)
# New tracker instance should load persisted data.
tracker2 = SoulVersionTracker(log_file)
assert len(tracker2.get_history(str(soul_file))) == 1
def test_get_current_hash(self, tmp_path):
soul_file = tmp_path / "SOUL.md"
soul_file.write_text("content", encoding="utf-8")
log_file = tmp_path / "versions.json"
tracker = SoulVersionTracker(log_file)
assert tracker.get_current_hash(str(soul_file)) is None
tracker.record(soul_file)
assert tracker.get_current_hash(str(soul_file)) is not None
# ---------------------------------------------------------------------------
# Integration: Timmy's actual soul.md
# ---------------------------------------------------------------------------
class TestTimmySoul:
"""Test that Timmy's existing soul.md parses without errors."""
def test_load_existing_soul(self):
soul_path = Path(__file__).parent.parent / "memory" / "self" / "soul.md"
if not soul_path.exists():
pytest.skip("Timmy's soul.md not found at expected path")
soul = SoulLoader.from_file(soul_path)
assert soul.identity or soul.raw_text # At minimum, text was loaded
assert len(soul.values) >= 1 # Timmy has values defined
# Need Path import for TestTimmySoul
from pathlib import Path # noqa: E402