fix(f4): harden schema-columns enrichment + ValidationError guard + staged-helper note
This commit is contained in:
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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()}`,
|
||||
|
||||
@@ -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)
|
||||
path = sync_schema_linking(session_id, cfg.paths.sessions)
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user