fix(harness): integrazione sessione L2 — _YamlModel to_yaml + config/tht.yaml symlink

Bug di porting emerso in L2 prep: tht session new falliva con
'AttributeError: SessionManifest has no to_yaml'. Lo stub locale _YamlModel in
session/models.py (placeholder pre-porting mschema) definiva solo
populate_by_name, senza i metodi to_yaml/from_yaml che store.py e session_cmd.py
usano. Aggiunti gli stessi metodi della controparte mschema (mantenendo
populate_by_name, necessario per db_schema alias='schema').

config/tht.yaml: symlink locale al workspace cliente attivo (psd.yaml). Il gate
chiama tht senza -c (default config/tht.yaml), quindi serve questo ponte per il
deployment per-cliente. Gitignored (per-cliente). .gitignore: + config/tht.yaml,
+ artifacts/.

Verifica: pytest 165 passed; tht session new crea sessione nel repo cliente;
pi vede i 3 tool reviewer_* (gate caricato); GLM 5.2 e' il model default di pi.
This commit is contained in:
2026-06-27 15:41:21 +02:00
parent 0a9ebddaec
commit 386ec3b833
2 changed files with 23 additions and 3 deletions
+4
View File
@@ -4,6 +4,10 @@ __pycache__/
.venv/ .venv/
sessions/ sessions/
indexes/ indexes/
artifacts/
# Per-customer active workspace (points at the customer repo via -c or symlink).
# The committed templates live in workspaces/*.example.yaml; config/ holds the active one.
config/tht.yaml
*.egg-info/ *.egg-info/
dist/ dist/
build/ build/
+19 -3
View File
@@ -1,14 +1,30 @@
from datetime import datetime from datetime import datetime
from typing import Literal from pathlib import Path
from typing import Literal, Self
import yaml
from pydantic import BaseModel, Field, ConfigDict from pydantic import BaseModel, Field, ConfigDict
# Stub locale di _YamlModel (in the reference implementation vive in mschema/models.py). # Stub locale di _YamlModel. In the reference implementation questa base vive in
# Qui serve solo come base con populate_by_name; mschema/ sara' portato nel Task A9. # mschema/models.py; qui la si replica perche' SessionManifest ha bisogno di
# populate_by_name=True (db_schema usa l'alias "schema"), che la base mschema non
# imposta. Identici to_yaml/from_yaml della controparte mschema.
class _YamlModel(BaseModel): class _YamlModel(BaseModel):
model_config = ConfigDict(populate_by_name=True) model_config = ConfigDict(populate_by_name=True)
def to_yaml(self, path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
data = self.model_dump(by_alias=True, mode="json", exclude_defaults=False)
path.write_text(
yaml.safe_dump(data, sort_keys=False, allow_unicode=True, width=120)
)
@classmethod
def from_yaml(cls, path: Path) -> Self:
raw = yaml.safe_load(path.read_text())
return cls.model_validate(raw)
class SessionManifest(_YamlModel): class SessionManifest(_YamlModel):
id: str id: str