feat(harness): add local session repository

This commit is contained in:
User
2026-07-16 17:11:22 +02:00
parent 8296937bc0
commit 8f364ec564
8 changed files with 464 additions and 8 deletions
+84
View File
@@ -1,10 +1,17 @@
import json
import os
import tempfile
import uuid
from datetime import datetime
from pathlib import Path
from typing import Literal, Self
import portalocker
import yaml
from pydantic import BaseModel, Field, ConfigDict
from tht.decisions import DecisionRecord
# Stub locale di _YamlModel. In the reference implementation questa base vive in
# mschema/models.py; qui la si replica perche' SessionManifest ha bisogno di
@@ -26,6 +33,74 @@ class _YamlModel(BaseModel):
return cls.model_validate(raw)
class PrincipalContext(BaseModel):
"""Trusted owner identity supplied by the runtime, never by a session document."""
issuer: str
subject: str
display_name: str | None = None
is_admin: bool = False
model_config = ConfigDict(frozen=True)
def _private_directory(path: Path) -> Path:
path.mkdir(parents=True, exist_ok=True)
if os.name != "nt":
path.chmod(0o700)
return path
def _write_private_json(path: Path, data: dict[str, str]) -> None:
fd, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
temporary = Path(temporary_name)
try:
with os.fdopen(fd, "w") as handle:
json.dump(data, handle, sort_keys=True)
handle.write("\n")
handle.flush()
os.fsync(handle.fileno())
if os.name != "nt":
temporary.chmod(0o600)
os.replace(temporary, path)
if os.name != "nt":
path.chmod(0o600)
finally:
temporary.unlink(missing_ok=True)
def local_principal(home: Path | None = None) -> PrincipalContext:
"""Load or create the stable UUID identity for a local ThothII home."""
if home is None:
from tht.config import local_tht_home
home = local_tht_home()
identity_path = _private_directory(home) / "identity.json"
lock_path = identity_path.with_suffix(".lock")
with portalocker.Lock(lock_path, mode="a+", timeout=10):
if identity_path.exists():
try:
identity = json.loads(identity_path.read_text())
principal = PrincipalContext.model_validate(identity)
except (json.JSONDecodeError, OSError, ValueError) as exc:
raise ValueError(f"Invalid local identity: {identity_path}") from exc
if principal.issuer != "local":
raise ValueError(f"Invalid local identity issuer: {identity_path}")
try:
parsed = uuid.UUID(principal.subject, version=4)
except ValueError as exc:
raise ValueError(f"Invalid local identity subject: {identity_path}") from exc
if str(parsed) != principal.subject or parsed.version != 4:
raise ValueError(f"Invalid local identity subject: {identity_path}")
return principal
principal = PrincipalContext(issuer="local", subject=str(uuid.uuid4()))
_write_private_json(
identity_path, {"issuer": principal.issuer, "subject": principal.subject}
)
return principal
class SessionManifest(_YamlModel):
id: str
created_at: datetime
@@ -47,6 +122,15 @@ class SessionManifest(_YamlModel):
group: str | None = None
class SessionSnapshot(BaseModel):
"""The current persisted session state, excluding the non-persistent chat stream."""
principal: PrincipalContext | None = None
manifest: SessionManifest
artifacts: dict[str, str] = Field(default_factory=dict)
decisions: list[DecisionRecord] = Field(default_factory=list)
class Candidate(BaseModel):
kind: Literal["table", "column"]
name: str