build(deps): pin pg-sprite to released v0.3.1 - #1333
Conversation
Adopts the invalid-index outcome split shipped in v0.3.0: pre-existing is replaced by abandoned, build-in-flight, builder-unobservable, other-table and not-droppable verdicts, and the two the executor marks permanent are routed to a refusal instead of an operational retry. v0.3.1 adds the typed create-shape causes and owned-relation read-back the PostgreSQL engine adopts in follow-on changes.
There was a problem hiding this comment.
🟢 Approval recommended
The dependency pin is paired with correct apply-path classification updates and comprehensive unit/integration coverage for the new invalid-index outcome vocabulary.
Pull request overview
Pins SchemaBot’s PostgreSQL engine dependency pg-sprite to released v0.3.1 and updates apply-time classification + operator-facing guidance to match pg-sprite’s expanded invalid-index outcome vocabulary, especially distinguishing operational (retryable) vs permanent (refusal) cases.
Changes:
- Bump
github.com/block/pg-spritefromv0.2.0tov0.3.1in the main module and the consumer module. - Update PostgreSQL apply error handling to treat only non-permanent invalid-index verdicts as operational, and map permanent verdicts to a refusal reason (
invalid-index-occupied) with code-specific operator guidance. - Expand/rename tests to cover the new invalid-index verdicts, including a new integration test asserting the “index name occupied on other table” permanent refusal.
File summaries
| File | Description |
|---|---|
| pkg/engine/postgres/apply.go | Updates invalid-index handling to separate operational vs permanent verdicts; adds code-specific operator guidance and new refusal mapping. |
| pkg/engine/postgres/apply_test.go | Extends unit coverage for the new invalid-index codes and pins executor code disposition totality. |
| pkg/engine/postgres/postgres_integration_test.go | Renames the prior invalid-index integration scenario and adds an “other table” permanent-refusal fixture. |
| go.mod | Pins github.com/block/pg-sprite to v0.3.1. |
| go.sum | Updates sums for github.com/block/pg-sprite v0.3.1. |
| e2e/consumermodule/go.mod | Pins consumer module’s indirect pg-sprite to v0.3.1. |
| e2e/consumermodule/go.sum | Updates consumer module sums for github.com/block/pg-sprite v0.3.1. |
Review details
- Files reviewed: 5/7 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
🤖 Adversarial correctness review — Correct change, and the interesting half is the direction it moves: two verdicts go from operational (retried against the same wall until the budget is spent, with advice telling an operator to drop something a drop cannot clear) to refused. That is the fail-closed direction, and the advice ladder now says the true next step for each. The pin itself is consistent — The claim I went after is the one the whole change rests on: can a verdict reach a disposition nobody chose? Two independent things close that, and it took checking both to be sure:
Together those are airtight for coverage. What they don't pin is agreement, which is the one thing I'd change.
1 — two authorities on permanence, and the seam between them is untested (low)Before this bump, I ran the comparison across all 26 codes. Two disagree:
I am not asking you to reclassify it, because I could not establish that operational is wrong. SchemaBot's vocabulary reasons about this differently from pg-sprite's: UX-5 says "an error in the operator's own input carries The finding is the seam, not the classification: after this change, a reviewer looking at // Divergences from pg-sprite's Permanent(): stricter is sanctioned by its
// own contract; looser needs a reason recorded here or it is a bug.
var sanctionedDivergence = map[executor.Code]string{
executor.CodeBudgetStatementExceeded: "sized as a lease; a budget SchemaBot declines to retry",
executor.CodePoolTooSmall: "deployment config, not the request (UX-5)",
}That turns the seam into one where a new upstream code, or a change to Verified — 6/6 mutations killed, the advice ladder against AV-8, and why refusing here is the safe directionMutation testing — 6 run, 6 killed. The one that matters is M1: deleting The advice ladder holds AV-8 and UX-4 across all seven arms. Only typed identifiers are interpolated, every arm ends in Refusing is the fail-closed direction for both new permanent codes.
Invariants. The PR cites UX-4 ("A refusal says what to do next") and upholds it — each of the seven arms names an action, and the unproven default's "some situations genuinely need a decision only an operator can make" is precisely what UX-4 permits. The entry it also moves and doesn't cite is UX-5, whose stated distinction is the one this change implements: "what separates Local run at This review was generated by Claude Code (claude-opus-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Approving. The pin is consistent across both modules and the vocabulary adoption moves in the fail-closed direction: two invalid-index verdicts stop being retried against a wall no retry can move, and each of the seven advice arms now names the true next step. Verified the load-bearing property — InvalidIndexError.Code() is total by construction and the pre-existing totality test over executor.Codes() pins every code to an explicit arm, so the discarded known flag at apply.go:387 is safe rather than lucky. 6/6 mutations killed; deleting the new Permanent() gate survives the unit suite and is caught only by the integration test this PR adds.
One low finding: the bump makes Code.Permanent() a second authority on permanence for one code family while refusalForOutcome keeps hand-maintaining all 26, and the totality test pins coverage but not agreement — the two already disagree on budget-statement-exceeded (stricter, and explicitly sanctioned by upstream's own contract) and pool-too-small (looser, and undocumented). I'm not asking for a reclassification; UX-5's reading may well make operational correct there. The ask is to assert the agreement with an allowlist of sanctioned divergences so the seam can't drift silently.
Upholds UX-4 and AV-8; UX-5 is the entry it also moves and is worth citing in the summary.
This stamp was left by Claude Code (claude-opus-5).
Resolves pkg/engine/postgres/apply.go against the cause/remedy refusal shape: the invalid-index-occupied arm and the typed verdict advice now supply a cause and a remedy so classifyRefusal composes the sequence-step clause between them and the remedy stays the last clause.
|
🤖 Follow-up on the merge — The head move is a merge of But the merge creates one interaction neither PR could have seen on its own, and it's the defect #1319 just finished fixing. #1319 established that the composed detail must survive the CLI status clamp (240 bytes, truncated from the tail) with the remedy's lead intact, and pinned it for create-collision. The invalid-index advice on this branch was written before that discipline existed, and now flows through the same composition. Measured at Two of the four shapes lose the remedy entirely — the operator reads a description of the problem and no instruction. The This is low severity, not a behavior bug — the full advice is intact in the published PR comment and the server log, and only the CLI Worth saying plainly: my original review could not have caught this. At My finding from the first review is unaffected: the operational arm still lists This review was generated by Claude Code (claude-opus-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Re-approving at 2106b409 — my earlier stamp was at d7b7ee86. The head move is a merge of origin/main, and the resolution is genuinely semantic: this branch's arms were adapted to #1319's typed cause/remedy fields rather than left on the flat detail, which is what keeps #1319's new cause assertion satisfied.
One low finding in the follow-up comment, created by the merge rather than by either PR: the invalid-index advice now flows through the composition #1319 taught to survive the 240-byte CLI status clamp, and two of its four shapes lose the remedy entirely (other-table and not-droppable at max-length identifiers, 396 B and 379 B). Same operator-facing gap #1319 just closed for create-collision, and the same fix applies. The Permanent() seam finding from my first review is unchanged.
This stamp was left by Claude Code (claude-opus-5).
…ne's permanence pg-sprite marks PoolTooSmall permanent: the pool is sized by the target DSN, so a retry is refused at admission the same way. The totality test now asserts each executor code's disposition against Code.Permanent(), with the one deliberate exception named, so a future vocabulary change fails the build instead of quietly landing in the wrong arm.
|
Review response from Kiran's (@Kiran01bm) code review assessment agent (Amp / Claude Opus 4.5) All six findings on #1333 are resolved in the follow-up commit or already on
"Verified correct" section (26-code enumeration, nested budget cause cannot shadow the verdict, reclassification is a bug fix): no action. Source: adversarial review of #1333 at d7b7ee8, generated by Kiran's code review agent (Claude Code / claude-opus-5); not posted on the PR. |
Pins pg-sprite to the released v0.3.1 and adopts its invalid-index outcome vocabulary in the PostgreSQL engine.
Why
The PostgreSQL engine was pinned to v0.2.0. Two releases have landed since. v0.3.0 splits the single "pre-existing invalid index" verdict into five, each carrying what the executor actually proved about the entry occupying the requested name, and marks two of them permanent via
Code.Permanent(): the name is held on a different table, or by an index the server will not drop concurrently (a partitioned table's index, an index partition, a constraint's index). Treating those as operational, as the engine did for every invalid-index verdict, would tell an operator to clear something that retrying cannot clear. v0.3.1 adds only library API — a typedCreateShapeCauseon create-path refusals andpreflight.LookupOwnedRelationNames— with no vocabulary or report-format change, so one pin move picks up both and the engine adopts the new API in small follow-on changes rather than here.What
go.modand the consumer module pin v0.3.1. Inpkg/engine/postgres/apply.gothe invalid-index arm of the apply path now handles only non-permanent verdicts as operational; permanent ones fall through toclassifyRefusal, which maps them to a newinvalid-index-occupiedrefusal reason. The operator advice ladder gains a per-code step: own leftover and abandoned name a drop; build-in-flight names the builder PID and says wait; builder-unobservable points atpg_stat_progress_create_indexwith a stats-reading role; other-table and not-droppable say rename in the schema file and re-plan. The unproven default still fails safe with investigation steps.CodeCancelledByCallerjoins the operational set so the outcome vocabulary stays total; the totality test overexecutor.Codepins that. Integration coverage renames the pre-existing fixture to the abandoned verdict it now produces and adds an other-table fixture asserting the refusal.Upholds UX-4: every invalid-index verdict still names the operator's next step, and the two permanent ones now say the true one (rename and re-plan) instead of a drop that would not help. No verdict is converted into a success outcome; the change only moves two from "retry after operator action" to "refused until the schema change changes".
Before / after
References