Skip to content

feat(api): key each rollout member's plan by the work it would run - #1423

Open
aparajon wants to merge 2 commits into
armand/multi-target-uxfrom
armand/multi-target-plan-fingerprint
Open

aparajon wants to merge 2 commits into
armand/multi-target-uxfrom
armand/multi-target-plan-fingerprint

Conversation

@aparajon

Copy link
Copy Markdown
Collaborator

When one database entry addresses several execution targets, the review comment has to describe what every member of the rollout will do. Today the rollup that classifies those members records how each one classified and then drops the change set that produced the classification, so a reader can say a member diverged but cannot show what it would actually run.

Rendering one block per member does not scale either: a converged fleet of five targets would repeat the same DDL five times. What a reader wants is one block per distinct plan. That needs a way to ask "do these two members run the same work?" without comparing every pair.

This adds that key and carries it, along with each member's change set, onto the rollup entry. Nothing renders it yet.

How members group

The key is built from the same canonicalized multiset the existing change set comparison is built on, so equal keys mean equal work rather than similar-looking DDL. Two members share a key exactly when the comparison reports their plans identical.

Before                                  After

rollup entry                            rollup entry
┌────────────────────────┐              ┌────────────────────────┐
│ deployment / target    │              │ deployment / target    │
│ class:  diverged       │              │ class:  diverged       │
│ diff:   1 unexpected   │              │ diff:   1 unexpected   │
│                        │              │ change set: ALTER ...  │
│ (change set discarded) │  ✗ no DDL    │ fingerprint: 9f2c...   │
└────────────────────────┘     to show  └───────────┬────────────┘
                                                    │ equal keys
                                                    ▼
                                        ┌────────────────────────┐
                                        │ one block per distinct │
                                        │ plan, not per target   │
                                        └────────────────────────┘

The fingerprint is opaque and deliberately not stable across releases: it is a grouping key for one rollup, never something to persist, compare across versions, or show an operator.

Failing closed

A member that could not be planned carries neither its change set nor a key, and keeps the cause it blocked on. Keying it would group it with members it was never compared against, and an ungrouped member renders as work nobody reviewed. This upholds MG-1: uncertainty about a member never becomes a passing check.

A member that did classify is always keyable, because both passing classifications are reached through a comparison that already canonicalized the member's content. The fail-closed branch for a keying failure is therefore unreachable defense in depth rather than an expected path.

Opened by Claude (Claude Opus 5).

🤖 Generated with Claude Code

aparajon and others added 2 commits September 17, 2026 10:33
A multi-target environment plans every member against its own live schema, so
one review round produces N plans. The plan comment has to group the members
that would run the same work, and the only honest way to decide "same work" is
the comparison the drift rollup already performs -- comparing rendered DDL text
would split a group over a backtick.

ChangeSetFingerprint returns a stable key over the same canonicalized multiset
CompareChangeSets keys on, so two change sets share a key exactly when the
comparison reports them identical. That makes grouping by it sound rather than
a heuristic: members of one environment are planned by the same differ against
the same desired schema, so the only thing that can make two plans differ is
the live schema each was diffed against, and any such difference changes the
multiset. A table test pins fingerprint equality against the comparison's own
verdict across restyled, reordered, duplicated, and diverging sets, so the two
cannot drift apart without failing.

Three properties the key needs and the tests pin by construction. It sorts the
rendered records, because a multiset and a set are both unordered and the
engine's return order is not work. It counts changes rather than collecting
them, because running a change twice is not running it once. And it separates
the fields it joins with a byte no field can hold -- a namespace, table, or DDL
body is whatever a schema author wrote, so a concatenated key would let a
namespace ending where a shard name begins collide two members into one group.

It errors on exactly what the comparison errors on, so a member whose change
set cannot be canonicalized has no key at all and a caller has to refuse to
group it rather than file it with members it was never compared against.

Nothing calls it yet; the plan comment's grouping lands on top of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A rollup entry recorded how a member classified but dropped the change
set that produced the classification, so a reader could say a member
diverged but not show what it would run. Every classified member now
carries its own change set and a fingerprint keying that change set by
the work it performs: members share the key exactly when the comparison
reports their plans identical, which lets a reader group members by plan
without comparing every pair.

A member that could not be planned carries neither. It has no plan to
describe, and keying it would group it with members it was never
compared against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@aparajon
aparajon marked this pull request as ready for review September 17, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant