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/src/infrastructure/soul/validator.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

227 lines
7.4 KiB
Python

"""Validate SOUL.md structure and check for contradictions.
Usage::
from infrastructure.soul.loader import SoulLoader
from infrastructure.soul.validator import SoulValidator
soul = SoulLoader.from_file("memory/self/soul.md")
issues = SoulValidator.validate(soul)
for issue in issues:
print(f"[{issue.severity}] {issue.section}: {issue.message}")
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from enum import StrEnum
from .loader import SoulDocument
class Severity(StrEnum):
"""Validation issue severity."""
ERROR = "error"
WARNING = "warning"
@dataclass
class ValidationIssue:
"""A single validation finding."""
severity: Severity
section: str
message: str
# Pairs of words that indicate potential contradiction when both appear
# in values or constraints.
_CONTRADICTION_PAIRS: list[tuple[str, str]] = [
("obey", "disobey"),
("always", "never"),
("silence", "verbose"),
("hide", "transparent"),
("deceive", "honest"),
("refuse", "comply"),
]
class SoulValidator:
"""Static validator for :class:`SoulDocument` instances."""
@classmethod
def validate(
cls,
soul: SoulDocument,
*,
base_soul: SoulDocument | None = None,
) -> list[ValidationIssue]:
"""Run all validation checks.
Returns a list of :class:`ValidationIssue` items. An empty list
means the document passed all checks.
If *base_soul* is provided, additional checks verify that the
extension does not contradict the base constraints.
"""
issues: list[ValidationIssue] = []
if not soul.is_extension:
issues.extend(cls._check_required_sections(soul))
issues.extend(cls._check_values(soul))
issues.extend(cls._check_constraints(soul))
issues.extend(cls._check_prime_directive(soul))
issues.extend(cls._check_contradictions(soul))
if base_soul is not None:
issues.extend(cls._check_extension_compatibility(soul, base_soul))
return issues
# -- Individual checks ---------------------------------------------------
@classmethod
def _check_required_sections(cls, soul: SoulDocument) -> list[ValidationIssue]:
"""Verify all five required sections are present."""
issues: list[ValidationIssue] = []
if not soul.identity:
issues.append(
ValidationIssue(Severity.ERROR, "identity", "Identity section is missing or empty")
)
if not soul.values:
issues.append(
ValidationIssue(Severity.ERROR, "values", "Values section is missing or has no entries")
)
if not soul.prime_directive:
issues.append(
ValidationIssue(
Severity.ERROR,
"prime_directive",
"Prime Directive section is missing or empty",
)
)
if not soul.audience_awareness:
issues.append(
ValidationIssue(
Severity.WARNING,
"audience_awareness",
"Audience Awareness section is missing or empty",
)
)
if not soul.constraints:
issues.append(
ValidationIssue(
Severity.ERROR,
"constraints",
"Constraints section is missing or has no entries",
)
)
return issues
@classmethod
def _check_values(cls, soul: SoulDocument) -> list[ValidationIssue]:
"""Check values section quality."""
issues: list[ValidationIssue] = []
if len(soul.values) > 10:
issues.append(
ValidationIssue(
Severity.WARNING,
"values",
f"Too many values ({len(soul.values)}). Consider trimming to 3-7.",
)
)
return issues
@classmethod
def _check_constraints(cls, soul: SoulDocument) -> list[ValidationIssue]:
"""Check constraints section quality."""
issues: list[ValidationIssue] = []
if len(soul.constraints) > 10:
issues.append(
ValidationIssue(
Severity.WARNING,
"constraints",
f"Too many constraints ({len(soul.constraints)}). "
"Consider consolidating to avoid paralysis.",
)
)
return issues
@classmethod
def _check_prime_directive(cls, soul: SoulDocument) -> list[ValidationIssue]:
"""Prime directive should be a single sentence."""
issues: list[ValidationIssue] = []
if not soul.prime_directive:
return issues
# Check for multiple sentences (crude: look for sentence-ending punctuation
# followed by a capital letter).
sentences = re.split(r"[.!?]\s+(?=[A-Z])", soul.prime_directive)
if len(sentences) > 1:
issues.append(
ValidationIssue(
Severity.WARNING,
"prime_directive",
"Prime directive appears to contain multiple sentences. "
"Consider reducing to exactly one.",
)
)
return issues
@classmethod
def _check_contradictions(cls, soul: SoulDocument) -> list[ValidationIssue]:
"""Detect potential contradictions within values and constraints."""
issues: list[ValidationIssue] = []
# Collect all text from values and constraints.
all_text = " ".join(soul.values.values()) + " " + " ".join(soul.constraints)
all_lower = all_text.lower()
for word_a, word_b in _CONTRADICTION_PAIRS:
if word_a in all_lower and word_b in all_lower:
issues.append(
ValidationIssue(
Severity.WARNING,
"contradictions",
f"Potential contradiction detected: '{word_a}' and '{word_b}' "
"both appear in values/constraints.",
)
)
return issues
@classmethod
def _check_extension_compatibility(
cls, extension: SoulDocument, base: SoulDocument
) -> list[ValidationIssue]:
"""Check that extension constraints don't contradict base constraints."""
issues: list[ValidationIssue] = []
base_text = " ".join(base.constraints).lower()
ext_text = " ".join(extension.constraints).lower()
for word_a, word_b in _CONTRADICTION_PAIRS:
if word_a in base_text and word_b in ext_text:
issues.append(
ValidationIssue(
Severity.ERROR,
"extension_compatibility",
f"Extension constraint contradicts base: base uses '{word_a}', "
f"extension uses '{word_b}'.",
)
)
if word_b in base_text and word_a in ext_text:
issues.append(
ValidationIssue(
Severity.ERROR,
"extension_compatibility",
f"Extension constraint contradicts base: base uses '{word_b}', "
f"extension uses '{word_a}'.",
)
)
return issues