extract migrate's pipeline into pkg/migrate, a library front door - #49
Conversation
The imperative pipeline (gate, resolve, introspect, classify, route, preflight, execute, one verdict) moves from internal/cli into pkg/migrate so embedding callers — the coming desired-state execution loop and orchestrator adapters — drive the same tested safety sequencing the CLI does, instead of re-implementing it around the binary. The CLI becomes a thin adapter; behavior, output, and exit codes are unchanged.
The review's coverage note: pkg/migrate had no test driving Run's own dispatch — it was exercised only through the CLI adapter. TestRunDispatch now covers gate re-check, both execute shapes, rewrite-required, backend-unavailable, and the before-verdict force-ack error directly. LiveFacts' bare 4-tuple (with a *bool tri-state) becomes the named migrate.Facts, matching the package's preflight.TargetFacts convention before the signature ships in a release.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
🤖 Adversarial correctness review, requested by @aparajon and performed by his agent. Reviewed at head Verdict: the "no behavior change" claim holds, and I have the evidence rather than the reading. 23 of 24 scenarios were byte-identical; the 24th differed only in a pgx-internal backend PID and slog attribute ordering under The scenarios covered success, substitution, refusal (exit 2), operational failure (exit 1), dry-run, JSON, the three force paths (valid ack / invalid ack / unqualified ack), the size guard, gated kinds, parse errors, the comment refusal, rename, and multi-op. Findings1. The first call teaches an embedder the zero value is fine; a later statement fails with a message that names neither 2. The library's verdicts speak CLI. Three operator-facing strings a library caller now ships to their users name pg-sprite's own flags: 3. (nit) 4. (nit) Action items
Verified (tried to break, couldn't)The behavioral claim is the one I attacked hardest and it survived everything. Beyond the 24-scenario differential run, I diffed the extracted original This review was generated by Claude Code (claude-opus-5). |
|
🤖 Second pass, same head ( Lens 1 — OSS adoptionThe PR body sells this as a refactor and it is a positioning change. Before this, pg-sprite had one library front door ( The on-ramp the sibling front door has and this one doesn't: a runnable example. And the question the example would answer that nothing currently does: what budgets should I pass? Worth noting for a later pass, not this one: the README still describes pg-sprite purely as a CLI. Nothing in it tells a Go developer that either front door is importable. Two front doors that are libraries is now a headline feature with no mention on the page most evaluators read first. Lens 2 — the seam an orchestrator consumesThe three-shape verdict-and-error contract is the right design and it is stated precisely enough to build against. Refusal → verdict with nil error; execution failure → failed verdict and the operational error; error with a zero verdict → stopped before a verdict, nothing executed. That maps cleanly onto what an orchestrator actually needs to distinguish: a decision it should surface to a human, a failure it should record with the committed prefix, and an infrastructure problem it should retry. The middle shape is the one most libraries get wrong by forcing a choice between an error and a result, and returning both — with the doc comment explicitly calling the verdict "the error's machine-readable twin" — is what lets the CLI keep exit 1 for operational failure while still printing the machine-readable failure. The stale artifact this PR should update is The flag-shaped strings in the verdict details (correctness finding 2) bite hardest at this seam, so I won't repeat the argument — but the framing worth carrying over is that an orchestrator does not have flags. When it renders One deliberate-looking choice worth confirming rather than discovering later: This review was generated by Claude Code (claude-opus-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Approving: the no-behavior-change claim holds under a 24-scenario differential run of base vs head against a live PostgreSQL 16, and the move is faithful. Findings are all on the new public API surface — see the two comments above; none block.
This review was generated by Claude Code (claude-opus-5).
…trings Adds migrate.DefaultOptions() (pinned to CLI flag defaults by test), early Options validation, a runnable Example_run, direct forced/failed Run coverage, discard-by-default Audit, and de-flags library verdict prose so each front door attaches its own spelling. Fills the Apply row in docs/schemabot-integration.md with the migrate.Run contract.
|
🤖 Review response — created by Kiran's code review agent (Amp, Claude Opus 4.5) — pull/49, COMMIT_HASH All four correctness findings (C1–C4) and both lenses addressed. Every library-surface finding is fixed in this PR rather than deferred, since this merge is the moment those strings and defaults become a library contract.
|
Extracts the imperative migrate pipeline out of
internal/cliintopkg/migrate— with this, both of pg-sprite's front doors (declarativepkg/diffplan, imperativepkg/migrate) are libraries with the same shape: parsed input in, one typed result out, the CLI reduced to an adapter. pg-sprite is now an embeddable engine, not only a CLI.Why
The migrate pipeline — gate, resolve, introspect, classify, route, preflight, execute, one verdict — is the engine's execution contract, but it lived inside the CLI, so the only way to run it was the
pg-spriteprocess. On PostgreSQL a single blocking ALTER can become a multi-statement online sequence inside the engine, so anything that executes more than one statement (declarative desired-state execution, embedding orchestrators) must drive this exact sequencing — per-statement gating, fresh live facts, budget-bounded execution — rather than re-implement it around the binary. Extracting it means those callers consume one tested pipeline, and pg-sprite's CLI users and orchestrator integrations get identical verdicts by construction. It also completes the symmetry: the declarative front door has been a library sincepkg/diffplan; this makes the imperative one match.What
pkg/migrate:Run(one parsed statement in, oneverdict.Verdictout),Gate(pre-dial statement-type gate, re-checked insideRun),Facts/ResolvedSchema(the introspection seam the dry-run plan shares), andOptions(budgets, retry, size guard, force acknowledgement, loggers).DefaultOptions(): the sanctioned embedding starting point — the same budgets, size guard, and retry the CLI's flag defaults wire, pinned together by test so the two cannot drift. The zeroOptionsis rejected byRunup front, by field name, before any database work.(verdict, nil); execution failure →(failed verdict, error)— the verdict is the error's machine-readable twin; error with zero verdict → the pipeline stopped before a verdict and nothing executed. Integration tests pin all three shapes directly onRun, including the forced-override path.--dry-run,--max-table-size, or--force— an orchestrator surfacing them in a PR comment or web UI never tells its users to run flags that don't exist there. Each front door attaches its own actionable spelling (the CLI points the CREATE TABLE gate refusal atpg-sprite diff --desired).Options.AuditfollowsLogger: nil discards; the CLI wires its always-on stderr audit handler itself, so no library default writes to a host process's stderr behind an embedder's logging stack. The verdict'sForcedfield remains the machine-readable record on every surface.Example_run(godoc front page), matchingpkg/diffplan'sExample_plan: parse → gate → connect →DefaultOptions→Run, with the three result shapes.internal/clibecomes a thin adapter: parse flags and SQL, early-gate before dialing, callmigrate.Run, render, map exit codes. Output and exit codes are unchanged apart from the de-flagged prose above; the CLI integration suite passes as-is.docs/schemabot-integration.md'sApplyrow now names the seam with the same specificity as thePlanrow:migrate.Run, its entry points, and the three-shape contract.pkg/migrateperiphery row (with the why-periphery footnote) and docs/architecture.md gains its package-map row.Deferred review findings