fix(harness): remediation difetti review — gate↔CLI, D15, D7/D6, D14, robustezza
Implementazione del piano di remediation progressiva sui difetti emersi dall'analisi dell'harness. Tutto verificato: 214 test Python (incl. L0 su Postgres reale), 14 test JS del gate, ruff pulito. Blocco 1 (CRITICA, integrazione gate↔CLI): - phase advance: gate usa --auto + exit 6; reviewer_confirm kind:phase fa advance esplicito che applica i prerequisiti (prima non avanzava per le fasi a conferma umana). - cte plan riceve i --name dal gate (param names); set-question con id posizionale; skill `tht search find`; nuovo comando `tht memory save-one` con dedup hash client-side in save_one_memory. Blocco 2 (D15, stato post-rollback): - campo `phase` su DecisionRecord + effective_decisions phase-aware per i subject "a nome" (cte_approved ecc.); _compute_promotions e finalize sulla vista effective; finalize confronta col piano CTE effettivo, non glob; `decision add --retracts` + comando `decision retract`. Blocco 3 (D7 read-only + D6 manifest): - assert_read_only su tutti e quattro i codepath (direct + REST); - manifest author/summary/updated_at/updated_by/schema_version popolati + helper touch_manifest sulle mutazioni. Blocco 4-5 (D14a/D14b): - decision_min_phase data-driven via `emits:` in workflow.yaml; - formula evidence: status auto, search_formulas, gruppo CLI `tht formula`, `search find --kind formula`, load_evidence_dir salta i .sql.md. Blocco 6 (robustezza): - taskdoc slice promoted_tables + bound enforced; report escaping/bound + rsplit note; filtro kind reader REST/direct; conteggio upserted robusto; guard REST run_query non-list; LSH disallineato -> LshIndexError. Blocco 7 (pulizia): - dead code gate e KIND_TO_TABLE morto rimossi; doc Postgres-only (README + connection.py). Blocco 0 (parziale): test di compatibilità firma gate↔CLI (tests/integration). Rinviati: fake-Pi runtime completo, artifact-gate da disco (#23), parità eligibility REST/direct (#28), unificazione reserved-labels (#30), memory_rejected da deselezione (#33). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,8 +14,8 @@
|
||||
// - the input lock + the `input` hook (entry detection + free-input block + `!` steer)
|
||||
// - the kickoff injection (before_agent_start) + the two kickoff payloads
|
||||
// - the agent_end prose safety net
|
||||
// - the exit-code contracts with the CLI (5 = gate refusal, 6 = needs human,
|
||||
// 7 = not-ready silent no-op)
|
||||
// - the exit-code contracts with the CLI (5 = gate refusal, 6 = needs human /
|
||||
// auto-advance not ready -> silent no-op)
|
||||
// - textResult / tht() / relayIfThtFails / advanceIfReady helpers
|
||||
//
|
||||
// TESTING: the pure builders are L1-tested (./gate/__tests__/). This file is the
|
||||
@@ -29,11 +29,8 @@ import {
|
||||
buildSelectRequest,
|
||||
buildMultiselectRequest,
|
||||
buildArtifactGate,
|
||||
buildInfoRequest,
|
||||
buildFreetextRequest,
|
||||
withChildLinkage,
|
||||
} from "./gate/builders.js";
|
||||
import { ALTRO, BACK_LABEL, QUIT_LABEL, CONTROL_LABELS, isReserved, stripReserved } from "./reserved-labels.mjs";
|
||||
import { isReserved, stripReserved } from "./reserved-labels.mjs";
|
||||
|
||||
// --- anti-bypass block lists (spec D4, verbatim from source L169-177) -----------
|
||||
const FORBIDDEN = [
|
||||
@@ -116,29 +113,22 @@ function phaseName(ctx, num) {
|
||||
const p = meta.phases.find((x) => x.num === num);
|
||||
return p ? p.name : "?";
|
||||
}
|
||||
function maxPhase(ctx) {
|
||||
return phaseMeta(ctx).max_phase;
|
||||
}
|
||||
// The schema-linking phase = the phase whose artifacts_out contains schema_linking.json.
|
||||
function schemaLinkingPhase(ctx) {
|
||||
const meta = phaseMeta(ctx);
|
||||
const p = meta.phases.find((x) => x.artifacts_out.includes("schema_linking.json"));
|
||||
return p ? p.num : 5;
|
||||
}
|
||||
|
||||
function currentPhase(ctx, session) {
|
||||
const out = tht(ctx, ["phase", "show", "--session", session]);
|
||||
const m = out.match(/Fase corrente:\s*(\d+)/);
|
||||
return m ? parseInt(m[1], 10) : 1;
|
||||
}
|
||||
|
||||
// tht phase advance --if-ready: exit 7 = not ready (silent no-op), others propagated.
|
||||
// tht phase advance --auto: exit 6 = not ready / needs human (silent no-op), others propagated.
|
||||
// Used for the fire-and-forget auto-advance of the auto phases (F2 memory, F6 cte) after a
|
||||
// reviewer_decide: it only advances when the phase is auto-eligible (zero substantive
|
||||
// decisions + prerequisites met), otherwise the CLI exits 6 and we no-op.
|
||||
function advanceIfReady(ctx, session) {
|
||||
try {
|
||||
tht(ctx, ["phase", "advance", "--if-ready", "--session", session]);
|
||||
tht(ctx, ["phase", "advance", "--auto", "--session", session]);
|
||||
return { advanced: true };
|
||||
} catch (e) {
|
||||
if (e.status === 7) return { advanced: false };
|
||||
if (e.status === 6) return { advanced: false };
|
||||
return { advanced: false, error: (e.stderr || e.message || String(e)).toString().trim() };
|
||||
}
|
||||
}
|
||||
@@ -191,7 +181,6 @@ export default function (pi) {
|
||||
let lockActive = false;
|
||||
let lastSteered = false;
|
||||
let pendingKickoff = null;
|
||||
let ollamaReady = false;
|
||||
let activeSessionId = null;
|
||||
|
||||
// 1) ANTI-BYPASS tool_call hook (spec D4, verbatim). Blocks direct phase/decision
|
||||
@@ -270,7 +259,6 @@ export default function (pi) {
|
||||
lockActive = false;
|
||||
lastSteered = false;
|
||||
pendingKickoff = null;
|
||||
ollamaReady = false;
|
||||
activeSessionId = null;
|
||||
_phaseMetaCache = null;
|
||||
});
|
||||
@@ -431,6 +419,8 @@ export default function (pi) {
|
||||
data: Type.Any(),
|
||||
version: Type.Optional(Type.Number()),
|
||||
}),
|
||||
// kind:"cte_plan" only -- ordered list of CTE names to persist (tht cte plan --name).
|
||||
names: Type.Optional(Type.Array(Type.String())),
|
||||
}),
|
||||
async execute(_id, params, _signal, _onUpdate, ctx) {
|
||||
lockActive = true;
|
||||
@@ -453,15 +443,28 @@ export default function (pi) {
|
||||
if (resp.control === "exit") return textResult("Il reviewer vuole uscire.");
|
||||
// approved -> execute the privileged action via the CLI.
|
||||
if (kind === "phase") {
|
||||
// Try auto-advance first; exit 6 = needs human (already handled by this dialog).
|
||||
const r = advanceIfReady(ctx, session);
|
||||
if (!r.advanced && r.error) return textResult(`${r.error} ${PHASE_RECOVERY}`);
|
||||
// Explicit human approval: advance unconditionally except for unmet
|
||||
// prerequisites. Plain `phase advance` (no --auto) enforces advance_problems
|
||||
// and exits 6 with the missing items, which relayIfThtFails surfaces.
|
||||
const err = relayIfThtFails(ctx, ["phase", "advance", "--session", session], PHASE_RECOVERY);
|
||||
if (err) return err;
|
||||
return textResult(`Fase approvata (sessione ${session}).`);
|
||||
}
|
||||
if (kind === "cte_plan") {
|
||||
const err = relayIfThtFails(ctx, ["cte", "plan", "--session", session], "");
|
||||
// The CTE plan is the ordered list of CTE names; the model passes them in
|
||||
// params.names (tht cte plan requires at least one --name).
|
||||
const names = Array.isArray(params.names) ? params.names : [];
|
||||
if (names.length === 0) {
|
||||
return textResult(
|
||||
"Nessun nome CTE fornito: il piano CTE richiede l'elenco ordinato dei CTE " +
|
||||
"(parametro names di reviewer_confirm).",
|
||||
);
|
||||
}
|
||||
const planArgs = ["cte", "plan", "--session", session];
|
||||
for (const n of names) planArgs.push("--name", n);
|
||||
const err = relayIfThtFails(ctx, planArgs, "");
|
||||
if (err) return err;
|
||||
return textResult(`CTE plan approvato (sessione ${session}).`);
|
||||
return textResult(`CTE plan approvato (${names.length} CTE, sessione ${session}).`);
|
||||
}
|
||||
if (kind === "cte_result" || kind === "sql") {
|
||||
const dt = kind === "sql" ? "sql_approved" : "cte_approved";
|
||||
@@ -499,7 +502,10 @@ export default function (pi) {
|
||||
assumps = [assumps];
|
||||
}
|
||||
}
|
||||
const args = ["session", "set-question", "--session", session, "--question", question];
|
||||
// `tht session set-question` takes the session id as a positional argument
|
||||
// (the `session` command group uses positional ids, unlike phase/cte/decision
|
||||
// which use --session).
|
||||
const args = ["session", "set-question", session, "--question", question];
|
||||
if (Array.isArray(assumps)) {
|
||||
for (const a of assumps) args.push("--assumption", String(a));
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ about a domain term, ask the reviewer.
|
||||
`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
|
||||
truth. The reviewer may reject them. Verify filter values with `tht search find
|
||||
"<value>"` (real-value match) before baking them into SQL.
|
||||
9. **Open ambiguities are explicit.** If an ambiguity can't be resolved, offer a
|
||||
`reviewer_decide` option "Leave ambiguity open" with a rationale, so the reviewer
|
||||
@@ -77,8 +77,8 @@ about a domain term, ask the reviewer.
|
||||
|
||||
Prerequisite: you must already be in Phase 1.
|
||||
|
||||
1. Explore the DWH and knowledge base: `tht search "<term>"` (evidence + schema, LSH
|
||||
over real values) and `tht search --kind evidence "<term>"`. The LSH exposes
|
||||
1. Explore the DWH and knowledge base: `tht search find "<term>"` (evidence + schema, LSH
|
||||
over real values) and `tht search find --kind evidence "<term>"`. The LSH exposes
|
||||
EVERY column where a value appears — it does not collapse to a single best match,
|
||||
so a value like "ablazione" may anchor on multiple columns.
|
||||
2. For each ambiguity (clinical term, population, time window, outcome), present a
|
||||
@@ -154,9 +154,10 @@ Prerequisite: Phase 3 closed.
|
||||
with a `value_grounded` option for each candidate column (the LSH exposes all of
|
||||
them, not collapsed to the best match). The reviewer chooses the anchor(s).
|
||||
4. **Concept formula (D14b).** If a concept (e.g. "fascia pediatrica", "stesso anno")
|
||||
has a candidate SQL formula (found in the evidence or derived from context),
|
||||
present it and let the reviewer approve/reject
|
||||
(`concept_formula_approved`/`concept_formula_rejected`).
|
||||
has a candidate SQL formula, retrieve it with `tht search find --kind formula
|
||||
"<concept>"` (or derive it from the evidence/context), present it, and let the
|
||||
reviewer approve/reject (`concept_formula_approved`/`concept_formula_rejected`).
|
||||
Reflect the approved formula in `schema_linking.json` (`concept_formulas`).
|
||||
5. Write `schema_linking.json` (the Phase 4 artifact) and close with
|
||||
`reviewer_confirm kind:"phase"`. Do NOT run `tht session check` (that's Phase 5).
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ by hand to the join.
|
||||
- Do the filters (WHERE/HAVING) reflect ALL the conditions of the rewritten question?
|
||||
- Are aggregations, groupings and orderings the required ones?
|
||||
- Empty or zero result: almost always indicates a problem in conditions or joins.
|
||||
Verify the filter values with `tht search "<value>"` (match on real values).
|
||||
Verify the filter values with `tht search find "<value>"` (match on real values).
|
||||
- Do the joins follow those promoted in `schema_linking.json`? (exception: the FK
|
||||
`data_time_key → dim_time.day_key` is not declared, see the time section.)
|
||||
- Time analyses: are you using `JOIN dim_time` and not key arithmetic?
|
||||
|
||||
Reference in New Issue
Block a user