feat: pristine harness JSON interfaces and require-existing semantic mode (P2)

This commit is contained in:
2026-08-11 18:40:11 +02:00
parent 3cfc8c53e6
commit ca391ba59c
12 changed files with 1204 additions and 252 deletions
+69 -15
View File
@@ -2,27 +2,57 @@
from __future__ import annotations
import hashlib
import json
import re
import hashlib
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")
def _evidence_json_context(config: Path):
from tht.cli.schema_cmd import _load_config_or_exit
return _load_config_or_exit(config)
def _evidence_json_payload(cfg, payload: dict, *, code: str, error: str | None = None) -> dict:
value = {
**payload,
"schemaVersion": 1,
"status": payload.get("status", "failed"),
"code": code,
"operation": "preprocess_evidence",
"workspaceId": cfg._workspace_id,
"workspaceRevision": cfg._workspace_revision,
}
if error is not None:
value["error"] = error
return value
def _simple_json_payload(*, code: str, error: str) -> dict:
return {
"schemaVersion": 1,
"status": "failed",
"code": code,
"operation": "preprocess_evidence",
"error": error,
}
def run_dwh_from_config(
config: Path, *, steps: tuple[str, ...], resume: str | None = None,
):
from tht.cli.lsh_cmd import build_lsh_artifacts
from tht.cli.schema_cmd import _load_config_or_exit, refresh_catalog
from tht.jobs.dwh_pipeline import (
DwhPreprocessPipeline, config_dwh_binding,
DwhPreprocessPipeline,
config_dwh_binding,
)
cfg = _load_config_or_exit(config)
@@ -87,7 +117,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=workspace_id_from_path(config),
workspace_id=cfg._workspace_id,
workspace_root=corpus_root.parent,
config_fingerprint=fingerprint(cfg.model_dump_json()),
input_fingerprint=fingerprint(config.resolve().as_posix()),
@@ -116,7 +146,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 = workspace_id_from_path(config)
pipeline.workspace_id = cfg._workspace_id
return pipeline.gc(workspace_root=corpus_root.parent, dry_run=dry_run)
@@ -133,7 +163,7 @@ def evidence_cmd(
if action == "gc":
try:
payload = gc_from_config(config, dry_run=dry_run)
except Exception:
except Exception: # noqa: BLE001
payload = {"status": "failed", "error": "evidence cleanup failed"}
if json_output:
typer.echo(json.dumps(payload, sort_keys=True))
@@ -145,8 +175,12 @@ def evidence_cmd(
else:
typer.echo(f"OK: evicted={len(payload['evicted'])} failures={len(payload['failures'])}")
return
cfg = _evidence_json_context(config) if json_output else None
if resume is not None and re.fullmatch(r"[0-9a-f]{32}", resume) is None:
payload = {"status": "failed", "error": "resume requires a preprocessing run id"}
payload = _simple_json_payload(
code="invalid_resume",
error="resume requires a preprocessing run id",
)
if json_output:
typer.echo(json.dumps(payload, sort_keys=True))
else:
@@ -154,23 +188,43 @@ def evidence_cmd(
raise typer.Exit(code=2)
try:
result = run_from_config(config, dry_run=dry_run, resume=resume)
except Exception:
payload = {"status": "failed", "error": "preprocessing failed"}
except Exception: # noqa: BLE001
payload = {"status": "failed"}
if json_output:
typer.echo(json.dumps(payload, sort_keys=True))
typer.echo(json.dumps(
_evidence_json_payload(
cfg,
payload,
code="preprocessing_failed",
error="preprocessing failed",
),
sort_keys=True,
))
else:
typer.secho("ERRORE: preprocessing failed", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1) from None
payload = result.model_dump(mode="json")
if payload.get("status") != "succeeded":
payload["error"] = "preprocessing job failed"
if json_output:
typer.echo(json.dumps(payload, ensure_ascii=False, sort_keys=True))
typer.echo(json.dumps(
_evidence_json_payload(
cfg,
payload,
code="preprocessing_failed",
error="preprocessing job failed",
),
ensure_ascii=False,
sort_keys=True,
))
else:
typer.secho("ERRORE: preprocessing job failed", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1)
if json_output:
typer.echo(json.dumps(payload, ensure_ascii=False, sort_keys=True))
typer.echo(json.dumps(
_evidence_json_payload(cfg, payload, code="ok"),
ensure_ascii=False,
sort_keys=True,
))
else:
counts = payload["counts"]
typer.echo(
@@ -205,7 +259,7 @@ def dwh_cmd(
raise typer.Exit(code=2)
try:
result = run_dwh_from_config(config, steps=selected, resume=resume)
except Exception:
except Exception: # noqa: BLE001
payload = {"status": "failed", "error": "DWH preprocessing failed"}
if json_output:
typer.echo(json.dumps(payload, sort_keys=True))