fix: candidates carry runnable T-SQL; placeholder sql is dropped, not rendered

Measured on a 2.7 MB production plan (plan-cxpaket.sqlplan, kept out of git):
- rewrite_sql becomes option_type + sql_to_run. The old name told the model 'query rewrite only', so an index option carried no CREATE INDEX anywhere in the pipeline and the report had zero SqlBlocks. It now requires paste-ready T-SQL per option, and the report prompt makes a SqlBlock per non-null sql_to_run a hard requirement.
- verify_semantics keys on option_type === 'rewrite' rather than 'has sql', so index and statistics options are not flagged just for carrying DDL.
- dropPlaceholderSql: 'SELECT ... FROM ... OPTION (MAXDOP 1)' is not runnable and was rendered as if it were. Ellipsis SQL is now nulled, and digestForModel reports sql_truncated so the model is told not to offer a rewrite for a statement it never received.
- max_tokens 2048 -> 4096 for both calls: the larger JSON outgrew the old ceiling and the user waited 38s for 'malformed JSON'. A truncated response now says how many characters it got, and extra params still override the ceiling.
- Real plan, live: 52s, 17 cards, 3 SqlBlocks with real table names and no placeholders, 3 ScoreBars, headline 'Query Plan Timeout on Parallel Join'. 43 unit, 23 e2e.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ahmed mohammed
2026-09-22 11:30:27 +03:00
co-authored by Claude Opus 4.7
parent 3fa436103a
commit 6248540857
7 changed files with 113 additions and 60 deletions
+4 -4
View File
@@ -50,11 +50,11 @@ function rank(c: Candidate, a: any): Ranked {
const composite = (Object.keys(WEIGHTS) as Dim[]).reduce((s, k) => s + WEIGHTS[k] * dims[k].value, 0);
const confs = Object.values(dims).map((d) => d.confidence).filter((x): x is number => x !== null);
const flags: string[] = [];
// Live Jev scores an index create around 0.57 ("probably equivalent, no proof stated"). A blanket floor
// flagged every index option, so the warning would be noise. Flag where semantics can actually move:
// any rewrite below the soft floor, or anything at all below the hard floor.
// Live Jev scores an index create around 0.57 ("probably equivalent, no proof stated"), so a blanket floor
// flags every index option and the warning becomes noise. Semantics only move when the query text changes:
// flag a rewrite below the soft floor, or anything at all below the hard floor.
const safety = dims.semantic_safety.value;
if (safety < SAFETY_HARD_FLOOR || (c.rewrite_sql !== null && safety < SAFETY_FLOOR)) flags.push("verify_semantics");
if (safety < SAFETY_HARD_FLOOR || (c.option_type === "rewrite" && safety < SAFETY_FLOOR)) flags.push("verify_semantics");
if (confs.length && Math.min(...confs) < CONFIDENCE_FLOOR) flags.push("low_confidence");
if (c.actions.some((x) => x.requires_change_control)) flags.push("change_control");
return { key: c.key, dims, composite: Number(composite.toFixed(4)), flags };