feat: harden runtime readiness and session workflow

This commit is contained in:
User
2026-07-14 10:27:25 +02:00
parent 6d7738d538
commit 6dbf93fff9
52 changed files with 1464 additions and 169 deletions
+5 -11
View File
@@ -145,11 +145,11 @@ def save_one_cmd(
json_out: bool = typer.Option(False, "--json", help="Output JSON (per Pi)."),
config: Path = CONFIG_OPT,
) -> None:
"""Upsert mirato (una riga) della memoria di una decisione su pgvector via writer key (D11).
"""Upsert mirato (una riga) della memoria di una decisione su pgvector (D11).
Promuove la decisione nel registro locale (idempotente) e fa un singolo upsert
remoto con dedup hash client-side -- niente full-resync. Abilita il salvataggio
di una memoria da postazione remota (workstation) con la sola writer key.
con dedup hash client-side -- niente full-resync. Il factory seleziona il writer
REST su workstation oppure il writer pgvector diretto sul profilo server.
"""
import json as _json
@@ -160,13 +160,6 @@ def save_one_cmd(
cfg = _load_config_or_exit(config)
manifest = load_session_or_exit(cfg, session)
require_vector_write_allowed(cfg, "memory save-one")
if not has_vector_write_rest(cfg):
typer.secho(
"ERRORE: `memory save-one` richiede la sezione `vector_write_rest` con una "
"API key di upsert nel workspace yaml (upsert remoto via writer key).",
fg=typer.colors.RED, err=True,
)
raise typer.Exit(code=4)
store = build_vector_store(cfg, require_write=True)
sdir = session_dir(cfg, session)
@@ -516,6 +509,7 @@ def solved_search_cmd(
from rich.table import Table
from tht.cli.vector_cmd import make_embedder, open_searcher
from tht.ports.vector import VectorReadUnavailable
from tht.solved import SOLVED_KIND
from tht.vectorstore.embeddings import EmbeddingsError
from tht.vectorstore.rest_client import VectorRestError
@@ -529,7 +523,7 @@ def solved_search_cmd(
searcher = open_searcher(cfg)
embedder = make_embedder(cfg.embeddings)
hits = searcher.search(embedder.embed_query(question), top_n=top, kinds=[SOLVED_KIND])
except (VectorRestError, EmbeddingsError, OperationalError) as e:
except (VectorRestError, VectorReadUnavailable, EmbeddingsError, OperationalError) as e:
typer.secho(
f"ATTENZIONE: exemplar non disponibili ({e}). Prosegui senza.",
fg=typer.colors.YELLOW, err=True,
+3 -2
View File
@@ -10,6 +10,7 @@ from pathlib import Path
import typer
from tht.cli.config_cmd import CONFIG_OPT
from tht.config import workspace_id_from_path
preprocess_app = typer.Typer(help="Materialize versioned preprocessing artifacts")
@@ -86,7 +87,7 @@ def run_from_config(config: Path, *, dry_run: bool = False, resume: str | None =
return "sha256:" + hashlib.sha256(value.encode()).hexdigest()
return pipeline.run_as_job(
workspace_id=config.stem.lower().replace(".", "-").replace("_", "-"),
workspace_id=workspace_id_from_path(config),
workspace_root=corpus_root.parent,
config_fingerprint=fingerprint(cfg.model_dump_json()),
input_fingerprint=fingerprint(config.resolve().as_posix()),
@@ -115,7 +116,7 @@ def gc_from_config(config: Path, *, dry_run: bool = False):
pipeline_version="evidence-v1",
retain_published_generations=cfg.vector.retain_published_generations,
)
pipeline.workspace_id = config.stem.lower().replace(".", "-").replace("_", "-")
pipeline.workspace_id = workspace_id_from_path(config)
return pipeline.gc(workspace_root=corpus_root.parent, dry_run=dry_run)
+5 -3
View File
@@ -5,6 +5,7 @@ import typer
from tht.cli.config_cmd import CONFIG_OPT
from tht.cli.schema_cmd import _load_config_or_exit
from tht.config import workspace_id_from_path
KIND_MAP = {
"evidence": ["evidence"],
@@ -59,7 +60,7 @@ def search_cmd(
cfg = _load_config_or_exit(config)
from tht.search.evidence import validate_corpus_workspace
workspace_id = config.stem.lower().replace(".", "-").replace("_", "-")
workspace_id = workspace_id_from_path(config)
validate_corpus_workspace(cfg, workspace_id)
dwh_snapshot = _leased_dwh_snapshot(cfg, ctx)
require_vector_cfg(cfg)
@@ -254,6 +255,7 @@ def pack_cmd(
from sqlalchemy.exc import OperationalError
from tht.cli.vector_cmd import make_embedder, open_searcher, require_vector_cfg
from tht.ports.vector import VectorReadUnavailable
from tht.search import combined_search, schema_tables
from tht.solved import SOLVED_KIND
from tht.vectorstore.embeddings import EmbeddingsError
@@ -262,7 +264,7 @@ def pack_cmd(
cfg = _load_config_or_exit(config)
from tht.search.evidence import validate_corpus_workspace
workspace_id = config.stem.lower().replace(".", "-").replace("_", "-")
workspace_id = workspace_id_from_path(config)
validate_corpus_workspace(cfg, workspace_id)
dwh_snapshot = _leased_dwh_snapshot(cfg, ctx)
require_vector_cfg(cfg)
@@ -271,7 +273,7 @@ def pack_cmd(
evidence: list[dict] = []
solved: list[dict] = []
warnings: list[str] = []
degrade = (VectorRestError, EmbeddingsError, OperationalError)
degrade = (VectorRestError, VectorReadUnavailable, EmbeddingsError, OperationalError)
vec = None
searcher = embedder = None
+42
View File
@@ -201,6 +201,27 @@ def show_cmd(
typer.echo(f"schema_linking.json: {'presente' if linking.exists() else 'assente'}")
@session_app.command("retrieval-pack")
def retrieval_pack_cmd(
session_id: str = typer.Argument(...),
config: Path = CONFIG_OPT,
) -> None:
"""Emette su stdout il retrieval pack persistito della sessione."""
cfg = _load_config_or_exit(config)
load_session_or_exit(cfg, session_id)
path = session_dir(cfg, session_id) / "retrieval_pack.md"
try:
content = path.read_text()
except OSError as exc:
typer.secho(
f"ERRORE: retrieval pack non disponibile per la sessione {session_id}: {exc}",
fg=typer.colors.RED,
err=True,
)
raise typer.Exit(code=1)
typer.echo(content, nl=False)
@session_app.command("close")
def close_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OPT) -> None:
"""Chiude la sessione (status=closed)."""
@@ -212,6 +233,27 @@ def close_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OPT)
typer.secho(f"OK: sessione {session_id} chiusa.", fg=typer.colors.GREEN)
@session_app.command("fail")
def fail_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OPT) -> None:
"""Registra un arresto del sistema non recuperabile automaticamente."""
from tht.session.store import fail_session
cfg = _load_config_or_exit(config)
load_session_or_exit(cfg, session_id)
fail_session(session_id, cfg.paths.sessions)
typer.secho(f"OK: sessione {session_id} marcata failed.", fg=typer.colors.RED)
@session_app.command("reopen")
def reopen_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OPT) -> None:
"""Riapre una sessione per una ripresa manuale."""
from tht.session.store import reopen_session
cfg = _load_config_or_exit(config)
load_session_or_exit(cfg, session_id)
reopen_session(session_id, cfg.paths.sessions)
typer.secho(f"OK: sessione {session_id} riaperta.", fg=typer.colors.GREEN)
@session_app.command("set-name")
def set_name_cmd(
session_id: str = typer.Argument(...),