docs: add execution-model page (autocommit-each-step, committed prefix) - #50
Conversation
The answer to "if a multi-step change fails halfway, what state is my table in?" was scattered across the JSON-contract docs and a runbook; this collects it into one human-first page and points to it from the README's Commands section.
"Implicit or bounded" was doing silent work: brief/VALIDATE steps run as one short explicit transaction (BEGIN so SET LOCAL budgets apply), while CREATE INDEX CONCURRENTLY is true autocommit on a dedicated session.
|
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: this is an excellent page and the mechanism half of it is accurate — I ran the exact scenario and the rendered verdict, JSON keys, exit code, error line, step SQL and constraint name all match what the page shows. The findings are all in one place: the page's reassurance about leftover state doesn't account for state the failed step itself leaves, and the repo's own Findings1. The opening promise is false for the INVALID index, and the page it links to says so. The lede says every state pg-sprite can leave behind is "documented, harmless to live traffic, and a step toward the desired schema — not debris." Two sections later the partial-state table lists an INVALID index ( 2. "An empty failed (budget-lock-exceeded)
table: public.users
statement: ALTER TABLE public.users ALTER COLUMN email SET NOT NULL
detail: sequence step 1 of 4 failed; no earlier steps had committed — Code names the outcome and any state the failed step itself left
failed at: step 1: ALTER TABLE "public"."users" ADD CONSTRAINT "users_email_not_null" CHECK ("email" IS NOT NULL) NOT VALIDThe verdict says "check 3. The same bullet's cause attribution is incomplete, which matters because the audience is automation. An empty 4. (nit) The library bullet omits the one field its own quoted error string displays. It lists Action items
Verified (tried to break, couldn't)Every mechanism claim on the page holds, and I checked the load-bearing ones by running the tool rather than reading it. The console block is byte-accurate against This review was generated by Claude Code (claude-opus-5). |
|
🤖 Second pass, same head ( Lens 1 — OSS adoptionThis is the highest-leverage page the project has shipped, and the PR body undersells it as a consolidation. "If it fails halfway, what state is my table in?" is not one question among many — it is the objection every online-DDL tool has to answer before anyone will run it against a table that matters, and it is the question an evaluator asks in the first ten minutes and a responder asks at 2am. Until now the answer existed only as contract fragments a reader had to assemble from four documents, which in practice means the answer did not exist: nobody assembles four documents while deciding whether to trust a tool. Putting it on one page, phrased as the question a human actually asks, is worth more than the sum of the fragments. Naming the "committed prefix" is the part that will outlive the page. A concept with a name is repeatable — it survives into the reader's own design review, their runbook, the summary they paste to their team. "Steps 1 through N−1 committed, step N rolled back, N+1 onward never attempted, no holes" is a precise invariant, and giving it a short handle is what lets someone advocate for the tool internally without re-deriving it. That is the same move the compiler-diagnostic grammar makes on the output side: borrow a vocabulary the reader can carry. Where the page stops short of what its own audience needs: it says what state you are in, never what to type. The partial-state table's retry column is one clause per row — "Resume at VALIDATE", "Drop the invalid index, re-run the build". For the invalid-index row that is fine, because invalid-index-recovery.md is a real runbook one link away. The other two rows have no such landing. A reader who just hit a failed And the honest sentence that is missing: nothing resumes automatically. The retry column reads as though a retry path is a feature; it is a description of what a human must do. An evaluator will ask whether the tool resumes on its own, and "no, deliberately — the engine never touches state it did not create, and here is the one-line recovery" is a much stronger answer than leaving them to infer it. Stating a limitation before it is discovered is the cheapest trust the project can buy, and this page is where it belongs. Finally, this is the page that makes the contention demo worth building — the one I have wanted since #39 and #42. There is now a written answer for what a lock pileup leaves behind, and a GIF of pg-sprite meeting a held lock, backing off, and naming the boundary is the same story told in the medium evaluators actually watch. The lede question is the caption. Lens 2 — the seam an orchestrator consumesThe closing paragraph is aimed squarely at embedders and it states the right contract. "Anything that executes more than one statement around the engine must report the statements already committed, the one that failed, and the ones never attempted" is exactly the obligation that propagates upward, and saying it explicitly means an orchestrator author reads it as a requirement on their surface rather than a property of pg-sprite's. That paragraph is the most reusable thing on the page. The mapping the page gives is complete on the machine side and I checked it against the real output: The one thing an embedder can get wrong from this page is auto-retry, and the table hides the distinction rather than showing it. The three rows differ in a way that decides whether a retry loop is safe: re-running VALIDATE is idempotent and safe to automate; re-issuing the Worth stating plainly, since the page is otherwise careful to keep consumers off prose: the partial-state table is keyed on the sequence, and no field on the verdict names which sequence ran. That is not a defect — an embedder submitted the statement, so it knows which row applies — but the page tells consumers to branch on 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 above. No blocking findings — every mechanism claim on the page checks out against the source and against a live run, and the four items are doc-accuracy fixes the author is trusted to fold in before merge. Action item 1 is the one worth not losing: the lede's "harmless to live traffic … not debris" is contradicted by docs/invalid-index-recovery.md for the INVALID-index row, and it points in the reassuring direction.
This stamp was left by Claude Code (claude-opus-5).
The lede promised every leftover state is harmless, but the INVALID index a failed concurrent build leaves is not — the page now says a failed step can leave state of its own, the empty-executed_sql bullet carries the verdict's caveat and the failed_step discriminator, the partial-state table gains a safe-to-automate column naming the invalid-index row as operator-only, and a worked example shows the real resume path (--force past the re-derived sequence; the duplicate_object SQLSTATE is the double-apply guard, not a resume).
|
Review response from Kiran's (@Kiran01bm) code review assessment agent (Amp / Claude Opus 4.5) Summary: all four correctness findings and every actionable lens item are fixed in 4109531; the contention demo is tracked as an internal follow-up; nothing rejected.
|
Summary
Adds
docs/execution-model.md— a human-first answer to the question every online-DDL tool must answer before anyone runs it against a table that matters: "if a multi-step schema change fails halfway, what state is my table in?" — and points to it from the README.Why
The autocommit-each-step contract, the committed prefix, and the per-sequence partial-failure contracts are all documented today, but only in machine-consumer form scattered across
plan-report.md,suggest-report.md,low-level-design.md, and the invalid-index runbook. A new user asking the plain failure-state question has no single page to read; this matters more as multi-statement surfaces grow.What
docs/execution-model.md: why PostgreSQL forbids a wrapping transaction for the online forms (and the two mechanical shapes behind autocommit-each-step), the committed prefix with diagrams, how the verdict/JSON/exit codes report the boundary (including the empty-executed_sqlcases and thefailed_stepdiscriminator), the per-sequence partial-state → retry-path table with a safe-to-automate column, and a worked recovery example for the mid-sequence failure.docs/README.md: index row.Before / after