cli: render dry-run as compiler diagnostics with exit-code contract - #36
Conversation
Replace the sectioned dry-run text with severity[code] diagnostics (rustc/Squawk style) laid out as labeled entries — label on its own line, content indented, blank line between entries. The typed planner/verdict reasons become rule codes, each linked to a per-code anchor in the online-DDL reference, with a Terraform-style plan summary line. Dry-run now exits 0 for an executable plan and 2 (the existing refusal code) otherwise, so CI can gate without parsing JSON. The JSON report and diff rendering are unchanged. Document every output shape with real captured examples in docs/cli-output-examples.md and add four representative samples (improve, refuse, lint, diff) to the README.
Flags bound to PGSPRITE_URL / PGSPRITE_CA_CERT resolve from the caller's shell, so TestURLIsRequiredForDatabaseCommands failed whenever the developer had PGSPRITE_URL exported (as the pre-push hook run does after local compose testing). Clear the bound variables in the test harness so required-flag and default-value assertions are hermetic.
…sary The JSON reports are the machine contract, so the examples doc shows only those; the human text rendering lives in the README samples. Add a real executed-verdict capture showing what "re-run without --dry-run" does, and a glossary table linking every diagnostic code used in the examples to its authoritative reference entry.
Each dry-run diagnostic code entry now opens with a uniform verdict/lock/scan/exit table so a reader landing on an anchor can scan the cost without parsing prose. metadata-only and online-idiom get per-operation lock tables: several metadata-only forms take only SHARE UPDATE EXCLUSIVE (and standalone CREATE TABLE locks nothing existing), and the online-idiom bucket splits between SHARE UPDATE EXCLUSIVE concurrent builds and brief-ACCESS EXCLUSIVE NOT VALID / USING INDEX catalog steps. The exit-code contract is stated once, with the codes; the examples doc links to it instead of restating it.
|
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 grammar holds up — every code, severity, wrap, and exit path I drove behaved as the doc says, and two shapes that used to slip through the old renderer now refuse loudly. Two findings, neither a regression: one code in the closed set has no doc anchor and disagrees with the code the run path prints for the same input, and the new exit contract inherits a fail-open case on a table that does not exist. Findings1. 2. The dry-run exit contract is fail-open on a table that does not exist — the one input error CI is most likely to make. 3. (nit) The single-step safer sequence gets a caveat written for multi-step ones. 4. (nit) The Action items
Verified (tried to break, couldn't)Drove Repro for findings 1 and 2 (live PG16, binary built at
|
|
🤖 Second pass on Lens 1 — OSS adoption easeThe "What it looks like" section is the single highest-leverage thing in this PR. A reader now learns what the tool does before deciding whether to install it, and the improve/refuse pair teaches the product's whole thesis in two screens. Keep doing this. The prompt chrome makes every sample uncopyable. All twelve code blocks in "CI can gate on the exit code without parsing JSON" is true only for refusals, and the destructive case is the one people will assume it covers. Exit 2 collapses four dispositions into one signal. Anchors point at Lens 2 — the seam an orchestrator consumesThe diagnostic vocabulary is now a public surface, and it is the right one. Codes are flat kebab-case tokens drawn from the typed reasons, the severity is derived rather than authored, and an unrecognized disposition refuses loudly instead of guessing — that fail-closed default is exactly what a caller needs, because the alternative is an engine that silently downgrades an unknown state to "executable". Two of the strings a caller might render are caller-influenced. The human renderer lives in The dry-run and run paths still describe the same refusal differently. Beyond the token mismatch in the adversarial comment: the run path's
This review was generated by Claude Code (claude-opus-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Approving on @aparajon's behalf after the adversarial correctness review and two-lens pass above. Neither finding is a regression — the dry run refused nothing before this PR and exited 0 unconditionally, so the exit contract narrows the hole rather than opening one — and nothing on the execution path changed. The action items are for follow-up, not fix-before-merge blockers. This stamp was left by Claude Code (claude-opus-5).
Refusals that need no live introspection (gate verdicts, unsupported statement kinds) now short-circuit before the DB connection; --force with --dry-run is rejected; the reason vocabulary gains the missing app-breaking-rename entry; exit-code docs are scoped per command; and docs_test pins every published JSON example to real pipeline output.
A planner-level refusal now reports unsupported-statement — the same typed reason the run path's refusal verdict carries — and names the refused operation. A dry run against a missing table sets table_exists: false, renders a table-not-found error, suppresses the apply footer, and exits with the refusal code so a typo'd table name cannot gate green. Single-step substitutions drop the multi-step commit caveat; the plan summary trims the server_version banner. Docs carry the new codes with anchors, the destructive-exits-0 caveat, and a renderer-safe-strings note for embedders; a table-driven test pins every emitted code to a reference-doc heading. Addresses review feedback on pull/36.
Sample blocks use console fences with a $ prompt and invoke the installed pg-sprite binary, matching the Install section; the real-session provenance moves to a one-line note per doc instead of per-block prompt chrome.
|
Review response from Kiran's (@Kiran01bm) AI code review assessment agent (Amp / Claude Opus 4.5) Summary: both correctness findings, both nits, and all adoption-lens gaps — including the sample copyability — are fixed; one item is a deliberate rejection (release-pinned doc anchors, tracked as an internal follow-up) and the exported code→summary map is deferred to the templates PR (#38). Fixes land in the follow-up commits on
|
Summary
Renders
migrate --dry-runoutput in a compiler-diagnostic grammar with typed rule codes, per-code doc anchors, a plan summary, and a dry-run exit-code contract CI can gate on without parsing JSON.Why
The previous dry-run output was a bare SQL echo with a comment block — a new user could not tell what would run, what was refused, or why "safer form" mattered, and CI had no way to distinguish a refused plan from an executable one without parsing the JSON report. Compiler-style diagnostics (
warning[safer-idiom]:,note:,help:) are the convention readers already know from rustc, Squawk, and ShellCheck.What
error[<code>]for refusals,warning/noteclassifications keyed by the planner's typed reasons, ahelp:block showing the safer sequence the engine would run,docs:links with one anchor per rule code, and a closingplan:/dry-run:/apply:summary.migrate's behavior, so CI can gate on the exit status alone.docs/cli-output-examples.md: a TOC'd catalog of every output shape (migrate, lint, diff) with JSON examples, the diagnostic-code glossary with per-code fact tables (verdict, exact lock level, scan/rewrite behavior, exit code), and a real execution example.PGSPRITE_*environment variables.Purpose and stack
This PR is the base of a short stack that makes pg-sprite's advisory output legible to humans and complete for automation, without touching execution behavior. Dry-run becomes a compiler-style diagnostic report (the advisory surface); the real run stays an execution receipt (the outcome record) — that split is deliberate. Stacked on this base: typed
guidanceon rewrite-required plan statements (format v2), the human renderers extracted into a templates package, and the same typed guidance carried on the run-path refusal verdict so the verdict seam a future orchestrator adapter consumes is field-complete before anything wires it up.