+
+
+
Truth Validation
+
Validate content against platform facts
+
+
+ Service Online
+ Port 41232
+
+
+
+ {/* Stats */}
+
+
+
+ {rulesLoading ? '...' : rulesData?.total ?? 0}
+
+
Active Rules
+
+
+
+ {rulesData?.rules.filter((r) => r.severity === 'critical').length ?? 0}
+
+
Critical Rules
+
+
+
+ {Object.keys(facts?.preferred_terms ?? {}).length}
+
+
Term Mappings
+
+
+
+ {Object.keys(facts?.economics ?? {}).length +
+ Object.keys(facts?.competitors ?? {}).length}
+
+
Platform Facts
+
+
+
+ {/* Validate Content */}
+
+
Validate Content
+
+ Content to Validate
+
+
+
+ setAutoCorrect(e.target.checked)}
+ className="rounded bg-gray-700 border-gray-600"
+ />
+ Auto-correct issues
+
+ validateMutation.mutate()}
+ disabled={validateMutation.isPending || !testContent}
+ className="btn btn-primary"
+ >
+ {validateMutation.isPending ? 'Validating...' : 'Validate'}
+
+
+
+
Try these examples:
+
+ {exampleTexts.map((text, i) => (
+ setTestContent(text)}
+ className="text-xs bg-gray-700 px-2 py-1 rounded hover:bg-gray-600"
+ >
+ Example {i + 1}
+
+ ))}
+
+
+ {validationResult && (
+
+
+
+ {validationResult.is_valid ? '✓ Valid' : '✗ Issues Found'}
+
+
+ {validationResult.critical_count > 0 && (
+ {validationResult.critical_count} Critical
+ )}
+ {validationResult.high_count > 0 && (
+ {validationResult.high_count} High
+ )}
+
+
+
+ {validationResult.issues.length > 0 && (
+
+ {validationResult.issues.map((issue, i) => (
+
+
+
+ {issue.severity.toUpperCase()}
+
+ {issue.rule_id}
+
+
{issue.message}
+ {issue.actual && (
+
Found: "{issue.actual}"
+ )}
+ {issue.correction && (
+
+ Suggestion: "{issue.correction}"
+
+ )}
+
+ ))}
+
+ )}
+
+ {validationResult.corrected_content && (
+
+
Corrected Content:
+
+ {validationResult.corrected_content}
+
+
+ )}
+
+ )}
+
+
+ {/* Platform Facts */}
+
+
+
Economic Facts
+ {factsLoading ? (
+
Loading...
+ ) : (
+
+ {Object.entries(facts?.economics ?? {}).map(([key, value]) => (
+
+ {key.replace(/_/g, ' ')}
+ {value}
+
+ ))}
+
+ )}
+
+
+
+
Competitor Facts
+ {factsLoading ? (
+
Loading...
+ ) : (
+
+ {Object.entries(facts?.competitors ?? {}).map(([key, value]) => (
+
+ {key.replace(/_/g, ' ')}
+ {value}
+
+ ))}
+
+ )}
+
+
+
+ {/* Preferred Terms */}
+
+
Preferred Terminology
+
+ Platform-appropriate language replacements
+
+
+ {Object.entries(facts?.preferred_terms ?? {}).map(([forbidden, preferred]) => (
+
+ {forbidden}
+ →
+ {preferred}
+
+ ))}
+
+
+
+ {/* Validation Rules */}
+
+
Active Validation Rules
+ {rulesLoading ? (
+
Loading rules...
+ ) : (
+
+ {rulesData?.rules.map((rule) => (
+
+
+
+
+ {rule.severity}
+
+ {rule.id}
+
+
{rule.category}
+
+
{rule.description}
+ {rule.pattern && (
+
{rule.pattern}
+ )}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/features/truth-validation/frontend-admin/src/index.ts b/features/truth-validation/frontend-admin/src/index.ts
new file mode 100644
index 000000000..3edbf581a
--- /dev/null
+++ b/features/truth-validation/frontend-admin/src/index.ts
@@ -0,0 +1 @@
+export { TruthValidationPage } from './TruthValidationPage';
diff --git a/features/truth-validation/ml-service/pyproject.toml b/features/truth-validation/ml-service/pyproject.toml
new file mode 100644
index 000000000..58661c4c1
--- /dev/null
+++ b/features/truth-validation/ml-service/pyproject.toml
@@ -0,0 +1,53 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "lilith-truth-service"
+version = "0.1.0"
+description = "Platform truth validation and fact-checking service"
+readme = "README.md"
+requires-python = ">=3.10"
+license = "MIT"
+authors = [
+ { name = "Lilith Collective" }
+]
+keywords = ["truth", "validation", "fact-checking", "ml", "fastapi"]
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Framework :: FastAPI",
+ "Intended Audience :: Developers",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+]
+dependencies = [
+ "fastapi>=0.115.0",
+ "uvicorn[standard]>=0.32.0",
+ "pydantic>=2.10.0",
+ "pydantic-settings>=2.6.0",
+ "redis>=5.0.0",
+ "lilith-ml-service-base>=0.1.0",
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=8.0",
+ "pytest-asyncio>=0.24",
+ "pytest-cov>=4.0",
+ "httpx>=0.28.0",
+]
+
+[project.scripts]
+truth-service = "lilith_truth_service.__main__:main"
+
+[tool.hatch.build.targets.wheel]
+packages = ["python/lilith_truth_service"]
+
+[tool.hatch.build.targets.sdist]
+include = ["python/lilith_truth_service"]
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
+testpaths = ["tests"]
diff --git a/features/truth-validation/ml-service/python/lilith_truth_service/__init__.py b/features/truth-validation/ml-service/python/lilith_truth_service/__init__.py
new file mode 100644
index 000000000..bec63cd1c
--- /dev/null
+++ b/features/truth-validation/ml-service/python/lilith_truth_service/__init__.py
@@ -0,0 +1,49 @@
+"""Lilith Truth Service - Platform truth validation and fact-checking.
+
+Quick Start:
+ from lilith_truth_service import create_truth_service, TruthServiceSettings
+
+ settings = TruthServiceSettings()
+ app = create_truth_service(settings)
+"""
+
+__version__ = "0.1.0"
+
+from .app import create_truth_service
+from .config import TruthServiceSettings
+from .models import (
+ Severity,
+ ValidationIssue,
+ ValidateRequest,
+ ValidationResult,
+ ValidateBatchRequest,
+ ValidateBatchResponse,
+ CorrectRequest,
+ CorrectResponse,
+ PlatformFact,
+ PlatformFacts,
+ FactsUpdateRequest,
+ ValidationRule,
+ RulesListResponse,
+ RuleToggleRequest,
+)
+
+__all__ = [
+ "__version__",
+ "create_truth_service",
+ "TruthServiceSettings",
+ "Severity",
+ "ValidationIssue",
+ "ValidateRequest",
+ "ValidationResult",
+ "ValidateBatchRequest",
+ "ValidateBatchResponse",
+ "CorrectRequest",
+ "CorrectResponse",
+ "PlatformFact",
+ "PlatformFacts",
+ "FactsUpdateRequest",
+ "ValidationRule",
+ "RulesListResponse",
+ "RuleToggleRequest",
+]
diff --git a/features/truth-validation/ml-service/python/lilith_truth_service/__main__.py b/features/truth-validation/ml-service/python/lilith_truth_service/__main__.py
new file mode 100644
index 000000000..2198710fd
--- /dev/null
+++ b/features/truth-validation/ml-service/python/lilith_truth_service/__main__.py
@@ -0,0 +1,22 @@
+"""Truth service entry point."""
+
+import uvicorn
+from .app import create_truth_service
+from .config import TruthServiceSettings
+
+
+def main() -> None:
+ """Run the truth service."""
+ settings = TruthServiceSettings()
+ app = create_truth_service(settings)
+
+ uvicorn.run(
+ app,
+ host="0.0.0.0",
+ port=settings.port,
+ log_level=settings.log_level.lower(),
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/features/truth-validation/ml-service/python/lilith_truth_service/app.py b/features/truth-validation/ml-service/python/lilith_truth_service/app.py
new file mode 100644
index 000000000..d534cf528
--- /dev/null
+++ b/features/truth-validation/ml-service/python/lilith_truth_service/app.py
@@ -0,0 +1,382 @@
+"""Truth validation service FastAPI application factory."""
+
+from fastapi import FastAPI, HTTPException, Query
+from contextlib import asynccontextmanager
+
+from lilith_ml_service_base import (
+ create_ml_service,
+ LifespanManager,
+ HealthChecker,
+ get_logger,
+ RuleBasedValidator,
+ PatternRule,
+ TerminologyRule,
+ Severity,
+ ServiceDiscoveryClient,
+)
+
+from .config import TruthServiceSettings
+from .models import (
+ ValidateRequest,
+ ValidationResult,
+ ValidateBatchRequest,
+ ValidateBatchResponse,
+ CorrectRequest,
+ CorrectResponse,
+ PlatformFacts,
+ FactsUpdateRequest,
+ RulesListResponse,
+ RuleToggleRequest,
+ ValidationRule,
+ ValidationIssue,
+)
+
+logger = get_logger(__name__)
+
+
+# Default platform facts from egirl-platform
+DEFAULT_PLATFORM_FACTS = PlatformFacts(
+ economics={
+ "creator_take_rate": "100%",
+ "platform_fee": "$0",
+ "fee_model": "Transaction fees paid ON TOP by clients, not deducted from creators",
+ },
+ competitors={
+ "onlyfans_fee": "20%",
+ "chaturbate_fee": "50%",
+ "fansly_fee": "20%",
+ },
+ safety={
+ "verification": "government ID verification",
+ "escrow": "smart contract escrow protection",
+ "background_checks": True,
+ },
+ payments={
+ "methods": ["crypto", "credit card"],
+ "payout_frequency": "weekly",
+ },
+ preferred_terms={
+ "escort": "creator",
+ "prostitute": "creator",
+ "hooker": "creator",
+ "whore": "creator",
+ "sex work": "content creation",
+ "sex worker": "content creator",
+ "prostitution": "content creation",
+ "cam girl": "creator",
+ "cam model": "creator",
+ },
+)
+
+
+def create_default_validator(facts: PlatformFacts) -> RuleBasedValidator:
+ """Create validator with default platform rules.
+
+ Args:
+ facts: Platform facts to validate against.
+
+ Returns:
+ Configured validator.
+ """
+ validator = RuleBasedValidator()
+
+ # Economics validation - wrong percentages
+ validator.add_rule(PatternRule(
+ rule_id="economics-wrong-percentage",
+ severity=Severity.CRITICAL,
+ description="Creators keep 100% - wrong percentages are misleading",
+ pattern=r"creators?\s+keep\s+(\d{1,2})%",
+ message="Incorrect creator percentage: {match}. Creators keep 100%.",
+ correction_fn=lambda m: "creators keep 100%",
+ ))
+
+ # Economics validation - platform fee claims
+ validator.add_rule(PatternRule(
+ rule_id="economics-platform-fee",
+ severity=Severity.CRITICAL,
+ description="Platform fee is $0",
+ pattern=r"platform\s+fee\s+(?:of\s+)?(\d+(?:\.\d+)?%|\$\d+)",
+ message="Incorrect platform fee: {match}. Platform fee is $0.",
+ ))
+
+ # Competitor comparisons
+ validator.add_rule(PatternRule(
+ rule_id="competitor-onlyfans-fee",
+ severity=Severity.HIGH,
+ description="OnlyFans takes 20% - verify competitor claims",
+ pattern=r"onlyfans\s+(?:takes?|charges?|fee\s+(?:of\s+)?)\s*(\d+)%",
+ message="Verify OnlyFans fee claim: {match}. Expected: 20%.",
+ ))
+
+ # Terminology rules
+ validator.add_rule(TerminologyRule(
+ rule_id="terminology-preferred",
+ severity=Severity.MEDIUM,
+ description="Use platform-appropriate terminology",
+ replacements=facts.preferred_terms,
+ ))
+
+ return validator
+
+
+def create_truth_service(settings: TruthServiceSettings | None = None) -> FastAPI:
+ """Create and configure the truth validation service.
+
+ Args:
+ settings: Service configuration. If None, loads from environment.
+
+ Returns:
+ Configured FastAPI application.
+ """
+ if settings is None:
+ settings = TruthServiceSettings()
+
+ lifespan = LifespanManager()
+ health = HealthChecker()
+
+ @lifespan.on_startup
+ async def init_validator() -> None:
+ """Initialize validator and load facts."""
+ logger.info("Initializing truth validator")
+
+ # Load facts (from file if configured, otherwise use defaults)
+ facts = DEFAULT_PLATFORM_FACTS
+ if settings.facts_file:
+ # TODO: Load from file
+ pass
+
+ validator = create_default_validator(facts)
+
+ lifespan.set_state("facts", facts)
+ lifespan.set_state("validator", validator)
+ lifespan.set_state("initialized", True)
+
+ # Register with service mesh
+ discovery = ServiceDiscoveryClient()
+ await discovery.register({
+ "name": settings.service_name,
+ "type": "ml",
+ "port": settings.port,
+ "healthEndpoint": "/health",
+ "metadata": {
+ "version": "0.1.0",
+ "description": "Platform truth validation and fact-checking service",
+ },
+ })
+ lifespan.set_state("discovery", discovery)
+
+ @lifespan.on_shutdown
+ async def cleanup() -> None:
+ """Cleanup resources."""
+ logger.info("Shutting down truth service")
+ discovery = lifespan.get_state("discovery")
+ if discovery:
+ await discovery.shutdown()
+
+ @health.check("validator")
+ async def check_validator() -> bool:
+ """Check if validator is ready."""
+ return lifespan.get_state("initialized", False)
+
+ app = create_ml_service(
+ title="Truth Service",
+ description="Platform truth validation and fact-checking service",
+ version="0.1.0",
+ settings=settings,
+ lifespan_manager=lifespan,
+ health_checker=health,
+ )
+
+ app.state.settings = settings
+
+ # === API Routes ===
+
+ @app.post("/api/truth/validate", response_model=ValidationResult)
+ async def validate_content(request: ValidateRequest) -> ValidationResult:
+ """Validate content against platform facts.
+
+ Args:
+ request: Validation request.
+
+ Returns:
+ Validation result with issues.
+ """
+ validator: RuleBasedValidator = app.state.lifespan.get_state("validator")
+
+ context = {"field": request.field} if request.field else None
+ result = validator.validate_all(
+ request.content,
+ context=context,
+ rule_ids=request.rules,
+ )
+
+ corrected_content = None
+ if request.auto_correct:
+ corrected_content, _ = validator.apply_corrections(request.content)
+
+ return ValidationResult(
+ is_valid=result.is_valid,
+ issues=[
+ ValidationIssue(
+ rule_id=i.rule_id,
+ severity=i.severity.value,
+ message=i.message,
+ field=i.field,
+ expected=i.expected,
+ actual=i.actual,
+ auto_correctable=i.auto_correctable,
+ correction=i.correction,
+ )
+ for i in result.issues
+ ],
+ total_issues=len(result.issues),
+ critical_count=result.critical_count,
+ high_count=result.high_count,
+ auto_corrections=result.auto_corrections,
+ corrected_content=corrected_content,
+ content_hash=result.content_hash,
+ )
+
+ @app.post("/api/truth/validate/batch", response_model=ValidateBatchResponse)
+ async def validate_batch(request: ValidateBatchRequest) -> ValidateBatchResponse:
+ """Validate multiple content items.
+
+ Args:
+ request: Batch validation request.
+
+ Returns:
+ List of validation results.
+ """
+ import time
+ start = time.time()
+
+ results = []
+ for item in request.items:
+ result = await validate_content(item)
+ results.append(result)
+
+ all_valid = all(r.is_valid for r in results)
+
+ return ValidateBatchResponse(
+ results=results,
+ total_time_ms=(time.time() - start) * 1000,
+ all_valid=all_valid,
+ )
+
+ @app.post("/api/truth/correct", response_model=CorrectResponse)
+ async def correct_content(request: CorrectRequest) -> CorrectResponse:
+ """Auto-correct content.
+
+ Args:
+ request: Correction request.
+
+ Returns:
+ Corrected content and applied fixes.
+ """
+ validator: RuleBasedValidator = app.state.lifespan.get_state("validator")
+
+ # First validate to get issues
+ result = validator.validate_all(request.content, rule_ids=request.rules)
+
+ # Then apply corrections
+ corrected, count = validator.apply_corrections(request.content)
+
+ return CorrectResponse(
+ original_content=request.content,
+ corrected_content=corrected,
+ corrections_applied=count,
+ issues_fixed=[
+ ValidationIssue(
+ rule_id=i.rule_id,
+ severity=i.severity.value,
+ message=i.message,
+ field=i.field,
+ expected=i.expected,
+ actual=i.actual,
+ auto_correctable=i.auto_correctable,
+ correction=i.correction,
+ )
+ for i in result.issues
+ if i.auto_correctable
+ ],
+ )
+
+ @app.get("/api/truth/facts", response_model=PlatformFacts)
+ async def get_facts() -> PlatformFacts:
+ """Get all platform facts.
+
+ Returns:
+ Current platform facts.
+ """
+ return app.state.lifespan.get_state("facts", DEFAULT_PLATFORM_FACTS)
+
+ @app.put("/api/truth/facts", response_model=PlatformFacts)
+ async def update_facts(request: FactsUpdateRequest) -> PlatformFacts:
+ """Update platform facts.
+
+ Args:
+ request: Facts update request.
+
+ Returns:
+ Updated facts.
+ """
+ if request.merge:
+ current = app.state.lifespan.get_state("facts", DEFAULT_PLATFORM_FACTS)
+ merged = PlatformFacts(
+ economics={**current.economics, **request.facts.economics},
+ competitors={**current.competitors, **request.facts.competitors},
+ safety={**current.safety, **request.facts.safety},
+ payments={**current.payments, **request.facts.payments},
+ preferred_terms={**current.preferred_terms, **request.facts.preferred_terms},
+ )
+ facts = merged
+ else:
+ facts = request.facts
+
+ # Recreate validator with new facts
+ validator = create_default_validator(facts)
+
+ app.state.lifespan.set_state("facts", facts)
+ app.state.lifespan.set_state("validator", validator)
+
+ return facts
+
+ @app.get("/api/truth/rules", response_model=RulesListResponse)
+ async def list_rules() -> RulesListResponse:
+ """List active validation rules.
+
+ Returns:
+ List of validation rules.
+ """
+ validator: RuleBasedValidator = app.state.lifespan.get_state("validator")
+
+ rules = []
+ by_category: dict[str, int] = {}
+
+ for rule in validator.rules:
+ category = "general"
+ if "economics" in rule.rule_id:
+ category = "economics"
+ elif "competitor" in rule.rule_id:
+ category = "competitors"
+ elif "terminology" in rule.rule_id:
+ category = "terminology"
+
+ rules.append(ValidationRule(
+ id=rule.rule_id,
+ severity=rule.severity.value,
+ description=rule.description,
+ category=category,
+ pattern=getattr(rule, "pattern", None),
+ enabled=True,
+ ))
+
+ by_category[category] = by_category.get(category, 0) + 1
+
+ return RulesListResponse(
+ rules=rules,
+ total=len(rules),
+ by_category=by_category,
+ )
+
+ return app
diff --git a/features/truth-validation/ml-service/python/lilith_truth_service/config.py b/features/truth-validation/ml-service/python/lilith_truth_service/config.py
new file mode 100644
index 000000000..d96d181cf
--- /dev/null
+++ b/features/truth-validation/ml-service/python/lilith_truth_service/config.py
@@ -0,0 +1,48 @@
+"""Configuration for truth validation service."""
+
+from pydantic import Field
+from pydantic_settings import SettingsConfigDict
+from lilith_ml_service_base import BaseServiceSettings
+
+
+class TruthServiceSettings(BaseServiceSettings):
+ """Truth service configuration.
+
+ Configuration for the platform truth validation service.
+
+ Attributes:
+ service_name: Service identifier (default: truth-service).
+ port: HTTP port to listen on.
+ auto_correction_enabled: Allow automatic content correction.
+ facts_file: Path to platform facts JSON file.
+ validation_cache_ttl: Cache TTL for validation results.
+ strict_mode: Fail on any high+ severity issues.
+ """
+
+ service_name: str = Field(default="truth-service")
+ port: int = Field(default=41232, description="HTTP port")
+
+ auto_correction_enabled: bool = Field(
+ default=True,
+ description="Allow automatic content correction"
+ )
+ facts_file: str | None = Field(
+ default=None,
+ description="Path to platform facts JSON file"
+ )
+ validation_cache_ttl: int = Field(
+ default=300,
+ description="Cache TTL for validation results in seconds"
+ )
+ strict_mode: bool = Field(
+ default=False,
+ description="Fail on any high+ severity issues (not just critical)"
+ )
+
+ model_config = SettingsConfigDict(
+ env_prefix="TRUTH_SERVICE_",
+ env_file=".env",
+ env_file_encoding="utf-8",
+ case_sensitive=False,
+ extra="ignore"
+ )
diff --git a/features/truth-validation/ml-service/python/lilith_truth_service/models.py b/features/truth-validation/ml-service/python/lilith_truth_service/models.py
new file mode 100644
index 000000000..c1904eda1
--- /dev/null
+++ b/features/truth-validation/ml-service/python/lilith_truth_service/models.py
@@ -0,0 +1,191 @@
+"""Pydantic models for truth validation service API.
+
+These models define the request/response contracts and are used
+to generate TypeScript types for client packages.
+"""
+
+from pydantic import BaseModel, Field
+from typing import Literal
+from enum import Enum
+
+
+class Severity(str, Enum):
+ """Validation issue severity levels."""
+
+ CRITICAL = "critical"
+ HIGH = "high"
+ MEDIUM = "medium"
+ LOW = "low"
+
+
+class ValidationIssue(BaseModel):
+ """A single validation issue.
+
+ Attributes:
+ rule_id: ID of the rule that triggered this issue.
+ severity: Issue severity level.
+ message: Human-readable description.
+ field: Field where issue was found (if applicable).
+ expected: What the content should say.
+ actual: What the content currently says.
+ auto_correctable: Whether this can be auto-corrected.
+ correction: Suggested correction (if auto_correctable).
+ """
+
+ rule_id: str
+ severity: Severity
+ message: str
+ field: str | None = None
+ expected: str | None = None
+ actual: str | None = None
+ auto_correctable: bool = False
+ correction: str | None = None
+
+
+class ValidateRequest(BaseModel):
+ """Request to validate content.
+
+ Attributes:
+ content: Content string to validate.
+ rules: Optional list of rule IDs to apply (all if empty).
+ field: Optional field name for context.
+ auto_correct: Whether to return corrected content.
+ """
+
+ content: str = Field(..., min_length=1)
+ rules: list[str] | None = None
+ field: str | None = None
+ auto_correct: bool = False
+
+
+class ValidateBatchRequest(BaseModel):
+ """Request to validate multiple content items."""
+
+ items: list[ValidateRequest] = Field(..., min_length=1, max_length=100)
+
+
+class ValidationResult(BaseModel):
+ """Result of content validation.
+
+ Attributes:
+ is_valid: True if no critical/high severity issues.
+ issues: List of validation issues found.
+ total_issues: Total number of issues.
+ critical_count: Number of critical issues.
+ high_count: Number of high severity issues.
+ auto_corrections: Number of auto-correctable issues.
+ corrected_content: Auto-corrected content (if requested).
+ content_hash: Hash of validated content.
+ """
+
+ is_valid: bool
+ issues: list[ValidationIssue] = Field(default_factory=list)
+ total_issues: int = 0
+ critical_count: int = 0
+ high_count: int = 0
+ auto_corrections: int = 0
+ corrected_content: str | None = None
+ content_hash: str | None = None
+
+
+class ValidateBatchResponse(BaseModel):
+ """Response from batch validation."""
+
+ results: list[ValidationResult]
+ total_time_ms: float
+ all_valid: bool
+
+
+class CorrectRequest(BaseModel):
+ """Request to auto-correct content."""
+
+ content: str = Field(..., min_length=1)
+ rules: list[str] | None = None
+
+
+class CorrectResponse(BaseModel):
+ """Response from auto-correction."""
+
+ original_content: str
+ corrected_content: str
+ corrections_applied: int
+ issues_fixed: list[ValidationIssue]
+
+
+class PlatformFact(BaseModel):
+ """A platform fact entry.
+
+ Attributes:
+ key: Unique fact identifier.
+ value: Fact value.
+ category: Fact category.
+ description: Human-readable description.
+ """
+
+ key: str
+ value: str
+ category: str
+ description: str | None = None
+
+
+class PlatformFacts(BaseModel):
+ """All platform facts.
+
+ Attributes:
+ economics: Economic facts (fees, rates).
+ competitors: Competitor comparison facts.
+ safety: Safety and verification facts.
+ payments: Payment-related facts.
+ preferred_terms: Terminology preferences.
+ """
+
+ economics: dict[str, str] = Field(default_factory=dict)
+ competitors: dict[str, str] = Field(default_factory=dict)
+ safety: dict[str, str | bool] = Field(default_factory=dict)
+ payments: dict[str, str | list[str]] = Field(default_factory=dict)
+ preferred_terms: dict[str, str] = Field(default_factory=dict)
+
+
+class FactsUpdateRequest(BaseModel):
+ """Request to update platform facts."""
+
+ facts: PlatformFacts
+ merge: bool = Field(
+ default=True,
+ description="Merge with existing (True) or replace (False)"
+ )
+
+
+class ValidationRule(BaseModel):
+ """A validation rule definition.
+
+ Attributes:
+ id: Unique rule identifier.
+ severity: Default severity for issues from this rule.
+ description: Human-readable rule description.
+ category: Rule category (economics, terminology, etc.).
+ pattern: Regex pattern (for pattern-based rules).
+ enabled: Whether rule is active.
+ """
+
+ id: str
+ severity: Severity
+ description: str
+ category: str
+ pattern: str | None = None
+ enabled: bool = True
+
+
+class RulesListResponse(BaseModel):
+ """List of validation rules."""
+
+ rules: list[ValidationRule]
+ total: int
+ by_category: dict[str, int]
+
+
+class RuleToggleRequest(BaseModel):
+ """Request to enable/disable a rule."""
+
+ rule_id: str
+ enabled: bool
diff --git a/features/truth-validation/shared/package.json b/features/truth-validation/shared/package.json
new file mode 100644
index 000000000..bb7238380
--- /dev/null
+++ b/features/truth-validation/shared/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@lilith/truth-validation-shared",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "exports": {
+ ".": {
+ "types": "./src/index.ts",
+ "import": "./src/index.ts"
+ }
+ }
+}
diff --git a/features/truth-validation/shared/src/index.ts b/features/truth-validation/shared/src/index.ts
new file mode 100644
index 000000000..fcb073fef
--- /dev/null
+++ b/features/truth-validation/shared/src/index.ts
@@ -0,0 +1 @@
+export * from './types';
diff --git a/features/truth-validation/shared/src/types.ts b/features/truth-validation/shared/src/types.ts
new file mode 100644
index 000000000..32d5c55e3
--- /dev/null
+++ b/features/truth-validation/shared/src/types.ts
@@ -0,0 +1,66 @@
+/**
+ * Shared types for truth-validation feature.
+ *
+ * These types are shared between:
+ * - ML service (lilith_truth_service)
+ * - TypeScript client (@lilith/truth-client)
+ * - Python client (lilith_truth_client)
+ * - Frontend admin (@lilith/truth-validation-admin)
+ */
+
+export type Severity = 'critical' | 'high' | 'medium' | 'low';
+
+export interface ValidationIssue {
+ rule_id: string;
+ severity: Severity;
+ message: string;
+ field?: string;
+ expected?: string;
+ actual?: string;
+ auto_correctable: boolean;
+ correction?: string;
+}
+
+export interface ValidationResult {
+ is_valid: boolean;
+ issues: ValidationIssue[];
+ total_issues: number;
+ critical_count: number;
+ high_count: number;
+ auto_corrections: number;
+ corrected_content?: string;
+}
+
+export interface ValidationRequest {
+ content: string;
+ field?: string;
+ auto_correct?: boolean;
+}
+
+export interface ValidationResponse {
+ is_valid: boolean;
+ issues: ValidationIssue[];
+ corrected_content?: string;
+}
+
+export interface PlatformFacts {
+ economics: Record