feat(gate): deterministic v2 review-gate payloads (cte_plan/cte_result/phase)

The tht-gate.js reviewer_confirm now builds structured v2 artifacts before the
widget so the reviewer approves gate-derived data, not raw model text:

- new pure modules gate/artifact-contracts.js (soft validators, {ok,errors},
  legacy-passthrough) and gate/enrich.js (index/description enrichment,
  buildCteResultV2 fusing thin model data with `tht cte info`, phase enrichment)
- cte_plan v2: validate + enrich + persist via `tht cte plan --name … --doc -`
  (names derived from data.ctes[]); legacy `names` param kept as fallback
- cte_result v2: rebuild from `tht cte next`/`tht cte info` (sql + preview from
  the persisted test record); null/error last_test -> actionable textResult
- phase v2: soft-validate + fill phase from meta + catalog descriptions
- prepareReviewerArguments coerces artifact.data too (GLM double-stringify);
  legacy markdown strings pass through unchanged
- SKILL.md: Phase 6 cte_plan payload A + thin cte_result guidance; Discipline 6
  payload C example; Discipline 7 reworded for gate-rebuilt cte_result

Legacy (non-v2) paths unchanged. TypeBox stays Type.Any() for artifact.data;
validation is soft (textResult) so models self-correct instead of looping.
All 102 gate JS tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 00:42:48 +02:00
co-authored by Claude Fable 5
parent 9b4f6b9804
commit 3ad93cd02f
8 changed files with 1278 additions and 18 deletions
+61 -10
View File
@@ -78,13 +78,38 @@ substantive decisions.
in the `message` (or in the `options`' labels/descriptions) a concise recap of the
context the reviewer needs to decide: what was asked, what you found, what each
option means. The reviewer does not see your internal reasoning — only the widget.
For a phase-closing gate (`reviewer_confirm kind:"phase"`) prefer the **structured
v2 recap** `artifact:{kind:"phase", data:{schema_version:2, …}}`: you author
`summary` (1-3 sentence markdown), `checks[]`, `sections[]` and `tables[]`; the gate
fills `phase` (from workflow meta) and every `description` from the catalog. Every
`sections[].items[]` MUST cite the concrete **table**, **column** and the **value**
that motivates the choice (booleans, time windows, thresholds) — not just prose.
Compact example:
```json
{"schema_version":2,"summary":"Selezionati pazienti attivi con ricoveri nel 2023.",
"checks":[{"label":"schema_linking valido","status":"ok"}],
"sections":[{"title":"Criteri di selezione","items":[
{"label":"solo pazienti attivi","table":"dim_patient","column":"flag_attivo",
"value":"IS TRUE","kind":"filter","rationale":"esclude i cessati"},
{"label":"finestra temporale","table":"dim_time","column":"year",
"value":"= 2023","kind":"filter","rationale":"anno richiesto"}]}],
"tables":[{"name":"dim_patient","role":"promoted",
"columns":[{"name":"cod_paz","value_filter":""}]}],
"open_questions":[]}
```
Legacy free-text recaps still work (no `schema_version`), but prefer v2. Note: the
F4 schema-linking recap travels in `tables` of this v2 phase payload — do NOT reuse
`kind:"schema_linking"` for a phase recap.
7. **Artifact = first-class output.** `schema_linking.json`, `cte_plan.json`,
`ctes/*.sql`, `sql_final.sql` are produced and reviewed explicitly, never hidden.
For CTE (`kind:"cte_result"`) and final SQL (`kind:"sql"`) the gate **reads the
file from disk and shows it integral** to the reviewer — so the file content is
what the reviewer approves. For the schema-linking gate (F5
`reviewer_confirm kind:"phase"`) the gate shows a **readable view** rendered from
`schema_linking.json`. Write the artifacts with care; they are the decision surface.
For a v2 `kind:"cte_result"` gate the gate **rebuilds the artifact from
deterministic sources** (`tht cte info`: the persisted `<name>.sql` + the last CTE
test record) — you send only the thin `{purpose?, rationale?, note?}` and the
reviewer approves the gate-built payload, not your text. For final SQL
(`kind:"sql"`) the gate reads `sql_final.sql` from disk and shows it integral. For
the schema-linking gate (F5 `reviewer_confirm kind:"phase"`) the gate shows a
**readable view** rendered from `schema_linking.json`. Write the artifacts with
care; they are the decision surface.
8. **Candidates are candidates, not truth.** Present LSH/vector/evidence matches with
their **provenance** (LSH / vector / evidence) and their scores, never as absolute
truth. The reviewer may reject them. Verify filter values with `tht search find
@@ -260,13 +285,39 @@ Prerequisite: Phase 5 closed.
1. Read `cte.md`. Decompose the rewritten question into CTEs (Agent View Generation):
each CTE captures an informative subset with a clear purpose, named in snake_case.
2. Present the full CTE plan to the reviewer (`reviewer_decide` with the plan).
2. Present the full CTE plan to the reviewer with `reviewer_confirm kind:"cte_plan"`,
passing a **structured v2 artifact** (`artifact:{kind:"cte_plan", data:{…}}`). You
author `question`, `strategy` and each `ctes[]` entry (`name`, `purpose`,
`rationale`, `depends_on`, `tables[].name`, `keys`, `filters[]` with
`column`/`op`/`value`/`rationale`, `output_columns`); the gate fills `index`
(1-based) and every `description` from the catalog, derives the ordered `--name`
list from `data.ctes[].name`, and on approval persists both `cte_plan.json` and the
chain doc (`cte_plan_doc.json`). Compact example (2 CTE):
```json
{"schema_version":2,"question":"pazienti attivi con almeno un ricovero nel 2023",
"strategy":"prima la base dei pazienti attivi, poi i loro ricoveri filtrati per anno",
"ctes":[
{"name":"base_pazienti","purpose":"pazienti attivi","rationale":"insieme di partenza",
"depends_on":[],"tables":[{"name":"dim_patient"}],"keys":["cod_paz"],
"filters":[{"column":"dim_patient.flag_attivo","op":"IS","value":"TRUE","rationale":"solo attivi"}],
"output_columns":["cod_paz"]},
{"name":"ricoveri_2023","purpose":"ricoveri dei pazienti nel 2023","rationale":"restringe al 2023",
"depends_on":["base_pazienti"],"tables":[{"name":"fact_ricoveri"}],"keys":["cod_paz"],
"filters":[{"column":"dim_time.year","op":"=","value":"2023","rationale":"finestra temporale"}],
"output_columns":["cod_paz","data_ricovero"]}
]}
```
3. For each CTE (in plan order): write `sessions/<id>/ctes/<name>.sql` (ONLY the
`WITH ... AS (...)` block, NO trailing SELECT), test with `tht cte test --session
<id> <name>`, present the result in `reviewer_confirm kind:"cte_result"`. The next
CTE is testable ONLY after the previous one is approved (CLI exit 5 if out of
order). Copy table/column names EXACTLY from the schema context; use values
verified with `tht search`.
<id> <name>` (with an **ok** outcome), then present it with
`reviewer_confirm kind:"cte_result"`. Pass ONLY the thin v2 data
`artifact:{kind:"cte_result", data:{schema_version:2, purpose?, rationale?, note?}}` —
NEVER paste SQL, columns or preview rows as text: the gate reads them
deterministically from `tht cte info` (the persisted `<name>.sql` + the last test
record) and builds the full artifact the reviewer approves. The next CTE is testable
ONLY after the previous one is approved (CLI exit 5 if out of order). Copy
table/column names EXACTLY from the schema context; use values verified with
`tht search`.
4. After the last CTE is approved, close with `reviewer_confirm kind:"phase"`.
## Phase 7 — Final SQL