fix(f4): harden schema-columns enrichment + ValidationError guard + staged-helper note

This commit is contained in:
2026-07-06 23:25:00 +02:00
committed by Marco Pancotti
parent 3ed570ee60
commit d942635085
4 changed files with 71 additions and 6 deletions
@@ -120,3 +120,51 @@ test("reviewer_schema_linking records table/column decisions and syncs schema_li
cp.execFileSync = origExecFileSync;
}
});
test("reviewer_schema_linking returns a textResult (not a throw) when a proposed table is not in the catalog", async () => {
const origExecFileSync = cp.execFileSync;
cp.execFileSync = (file, args) => {
if (args[0] === "phase" && args[1] === "meta")
return JSON.stringify({ phases: [{ num: 4, id: "F4" }] });
if (args[0] === "phase" && args[1] === "show") return "Fase corrente: 4\n";
if (args[0] === "schema" && args[1] === "columns" && args[2] === "badtable") {
const err = new Error("Command failed");
err.stderr = "ERRORE: tabella 'badtable' non trovata nel catalogo";
throw err;
}
return "";
};
try {
const gate = require(GATE);
const { createFakePi } = require("./fake_pi_runtime.js");
const { pi, ctx, tools } = createFakePi();
ctx.cwd = "/nonexistent-thothii-test-cwd";
gate.default(pi);
const tool = tools.get("reviewer_schema_linking");
assert.ok(tool, "reviewer_schema_linking must be registered");
const result = await tool.def.execute(
"call-1",
{
session: "s1",
title: "Schema linking",
tables: [
{
id: "t-bad",
name: "badtable",
kind: "promote",
},
],
},
null,
null,
ctx,
);
assert.match(result.content[0].text, /badtable/);
} finally {
cp.execFileSync = origExecFileSync;
}
});
+13 -4
View File
@@ -651,10 +651,19 @@ export default function (pi) {
const phase = phaseId(ctx, currentPhase(ctx, session));
// Enrich each table with its full catalog columns (deterministic source).
const enriched = tables.map((t) => {
const cat = JSON.parse(tht(ctx, ["schema", "columns", t.name, "--json"]));
const enriched = [];
for (const t of tables) {
let cat;
try {
cat = JSON.parse(tht(ctx, ["schema", "columns", t.name, "--json"]));
} catch (e) {
const msg = (e.stderr || e.message || String(e)).toString().trim();
return textResult(
`Tabella '${t.name}' non caricabile dal catalogo (${msg}). Proponi solo tabelle presenti nel catalogo (usa 'tht schema render' / 'tht search' per verificarne i nomi).`,
);
}
const suggested = new Set(t.suggested_columns ?? []);
return {
enriched.push({
id: t.id,
name: t.name,
kind: t.kind,
@@ -662,8 +671,8 @@ export default function (pi) {
description: cat.description ?? "",
rationale: t.rationale ?? "",
columns: cat.columns.map((c) => ({ ...c, suggested: suggested.has(c.name) })),
};
});
}
const widget = buildSchemaLinkingRequest({
id: `u${Date.now()}`,
+6
View File
@@ -147,11 +147,17 @@ def sync_schema_linking_cmd(
config: Path = CONFIG_OPT,
) -> None:
"""Riproietta schema_linking.json dalle decisioni F4 del ledger (deterministico)."""
from pydantic import ValidationError
from tht.session.store import sync_schema_linking
cfg = _load_config_or_exit(config)
load_session_or_exit(cfg, session_id)
try:
path = sync_schema_linking(session_id, cfg.paths.sessions)
except ValidationError as e:
typer.secho(f"ERRORE: schema_linking non valido:\n{e}", fg=typer.colors.RED, err=True)
raise typer.Exit(code=5)
typer.secho(f"OK: schema_linking.json riproiettato ({path}).", fg=typer.colors.GREEN)
+2
View File
@@ -54,6 +54,8 @@ def promoted_tables_for(cfg, session_id: str | None) -> set[str] | None:
}
# Staged for a future SQL-projection consumer; hard column enforcement is a
# documented non-goal (Option-1 soft) — not yet wired into validate_or_exit.
def promoted_columns_for(cfg, session_id: str | None) -> set[str] | None:
if session_id is None:
return None