feat(harness): persist CTE preview rows, add tht cte info, --doc for cte plan

WS1 of review-gates-v2: gives the JS gate (WS2) deterministic data to build the
cte_result v2 payload.

- CteTestRecord gains optional preview_rows (JSON-coerced, truncated cells);
  test_cmd populates it from the bounded result rows.
- New read-only `tht cte info <name> --session <id> [--json]`: plan
  index/total, persisted .sql, cte_plan_doc.json entry (if any), last
  CteTestRecord. Exits 1 with a clean stderr message on missing
  session/plan/name/sql.
- `tht cte plan --doc -` validates a chain-doc JSON (ctes[].name must match
  --name, same order) and writes it to cte_plan_doc.json; cte_plan.json stays
  a plain list[str] (load-bearing for tht.phase.next_cte). --doc is optional.
This commit is contained in:
2026-07-07 00:23:50 +02:00
parent 7491e8c3ac
commit 9b4f6b9804
5 changed files with 453 additions and 3 deletions
+15
View File
@@ -25,6 +25,21 @@ class CteTestRecord(BaseModel):
execution_ms: int = 0
warnings: list[str] = []
error: str | None = None
preview_rows: list[list[str | int | float | bool | None]] | None = None
_MAX_CELL_CHARS = 200
def _jsonable(v):
"""Coerce a raw DB cell to a JSON-native value for persistence in cte_tests.json.
None/bool/int/float pass through unchanged; str (and anything else, via str())
is truncated to ~200 chars."""
if v is None or isinstance(v, (bool, int, float)):
return v
s = v if isinstance(v, str) else str(v)
return s[:_MAX_CELL_CHARS]
def has_trailing_select(cte_sql: str) -> bool: