cli: substitute safer sequences by default, gated --force, stable executor codes - #30
Conversation
migrate now executes the planner's classified route: a blocking submitted form runs as the safer native sequence (reported in the verdict's executed_sql), --force requires typing the resolved schema-qualified table and is audit-logged, and every typed executor outcome carries a stable string code (executor.OutcomeCode) so orchestrators branch on one vocabulary, not prose.
0468131 to
fa357bd
Compare
…ptNative A forced submitted form was routed through RunSequence, whose shape admission categorically refused it before any DB access — the documented --force CREATE INDEX case could never execute. Forced runs now bypass sequence admission and execute as one blind bounded attempt. Executor admission refusals (unnamed index, IF NOT EXISTS, undriven DETACH PARTITION CONCURRENTLY) now surface as typed refusal verdicts instead of raw operational errors, invalid-index debris is never masked as a plain budget refusal, refused forced attempts keep the machine-readable forced flag, and the statement-budget advice is tailored for submitted online idioms.
…to kiran01bm/p3-4-substitution * origin/kiran01bm/p3-2-native-idioms: surface retry attempts and prove lock-retry flag wiring feat(executor): lock_timeout + bounded retry for native DDL # Conflicts: # internal/cli/cli.go # internal/cli/migrate.go # internal/cli/migrate_test.go
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
morgo
left a comment
There was a problem hiding this comment.
🤖 Approving on Morgan's behalf (automated review; pg-sprite liberal bar).
The core safety story checks out: substituted sequences are semantically equivalent-or-safer idioms (CREATE INDEX → CONCURRENTLY; SET NOT NULL → NOT VALID CHECK + VALIDATE + SET NOT NULL + scaffold drop), disclosed in the verdict's executed_sql; rewrite-requiring changes refuse up front instead of burning an ACCESS EXCLUSIVE attempt; every refusal fails closed. The --force gate only unlocks the substitution-override/rewrite/backend-unavailable paths (planner refusals stay unforceable), keeps the size guard and budgets, and always leaves an audit trail — each of these is pinned by an integration test. The new executor codes are additive (no renumbering; refusal is still exit 2), and SQL construction follows the repo's quoting conventions (parameterized catalog lookup + AST-deparser re-qualification with the comment guard).
Two non-blocking notes worth follow-ups:
-
Partial-sequence state isn't machine-readable yet. A substituted sequence that fails mid-way leaves a committed prefix (e.g. a surviving
CHECK … NOT VALIDscaffold that does enforce on new writes if VALIDATE fails).SequenceStepErrordiscloses the failed step and committed prefix, but only as a typed operational error on stderr — the verdict JSON doesn't carry the new stable executor codes yet (the docs list this as remaining work). Until that lands, automation can't distinguish "nothing happened" from "scaffold left behind." -
Observable-outcome changes for automation callers. Plain
CREATE INDEXused to refuse (exit 2,ReasonIndexStatement) and now succeeds via CIC substitution (exit 0);ALTER COLUMN TYPEused to blind-attempt then budget-cancel and now returnsbackend-unavailablewith no attempt. Both are intended and strictly safer, but anything branching on the old outcomes will see different results for identical inputs — worth a changelog callout.
Nits (no action needed): checkForceAck requires the internally-folded identifier form (fails closed on mismatch); --force that turns out unnecessary is a silent no-op with forced:false; JSON tags on the (unreleased) executor report structs rename their marshaled keys, now pinned by a contract test.
…angelog Review follow-ups on the substitution PR: a mid-sequence failure was only a typed error on stderr, so automation could not distinguish nothing committed from a scaffold left behind. Execution failures now end in a third verdict outcome (failed, still exit 1) carrying executor.OutcomeCode, the failed step, and the committed prefix. The changelog calls out the intended outcome changes for automation callers.
|
Review response from Kiran's (@Kiran01bm) AI code review assessment agent (Amp, Claude Opus 4.5) Summary: Both non-blocking follow-ups are fixed on this branch — execution failures now emit a machine-readable
Confirmations (no action): the review's verified-correct items — equivalent-or-safer substitutions disclosed in |
Re-land #30 substitution work on main
Summary
migratenow executes the planner's classified route instead of a blind optimistic attempt: when the submitted form blocks and a safer native sequence exists, the engine substitutes and runs the safer form by default; the rare run-the-literal case goes through a gated, audited--force. Every typed executor outcome now carries a stable string code, the same treatmentpkg/lintgave its findings.What
migrateclassifies and routes the statement exactly as dry-run would, then executes the routed SQL through the sequence executor. A blockingCREATE INDEXruns as its concurrent build; aCHECK/FK constraint runs asNOT VALID+VALIDATE; the verdict's newexecuted_sqlfield reports the substituted steps so automation sees exactly what ran. A planner recommendation the engine cannot construct executable SQL for refuses (not-native-safe-rewrite-required) rather than running the form the plan itself flagged.--force. The flag's value must name the resolved schema-qualified target table exactly — a typed acknowledgement, not a-y. The override is warn-logged unconditionally and recorded machine-readably in the verdict'sforcedfield. Force overrides routing only: budgets, the statement gate, preflight, and the size guard still apply, and planner refusals (no known safe path) are not forceable.search_pathand re-emits the qualified statement; the library-levelErrUnqualifiedTablerefusal stays.executor.Codeis a closed kebab-case vocabulary;OutcomeCodemaps any executor error to its code (aSequenceStepErrorcarries its failed step's cause), and the sequence/index reports gain JSON tags with a pinned wire-shape test.preflight.NoSizeLimitlets planner-proven online sequences run on large tables — long work there is the pattern's purpose — while blind and forced attempts of the submitted form stay guarded.Why
Rendering safer sequences without executing them left the engine advisory at exactly the moment it matters: the classifier knows the safe idiom, so the default path should run it. The stable code vocabulary is the seam an orchestrator consumes — branch on codes, never prose.
Before / after