Skip to content

feat(api): fan an apply out to one operation per rollout member - #1270

Draft
aparajon wants to merge 7 commits into
armand/multi-target-member-plansfrom
armand/multi-target-apply-fanout
Draft

feat(api): fan an apply out to one operation per rollout member#1270
aparajon wants to merge 7 commits into
armand/multi-target-member-plansfrom
armand/multi-target-apply-fanout

Conversation

@aparajon

@aparajon aparajon commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Why this matters

An apply builds one operation per deployment and runs the apply's single plan on all of them. For an environment whose members were planned independently, that means every member runs the primary's DDL — DDL written for a different target's schema. This is the apply half of the multi-target contract: each member executes the plan it was actually planned from.

It also closes a latent collision that exists independently of the targets: spelling. The sharded operation group key was built from the deployment alone, so two members sharing a deployment would collide on a key and one member's work would be filed under the other's.

What it does

Resolves every rollout member to a plan before building operations, then keys the operations by member:

apply creation
  │
  ├─ mirrored     → every member runs the apply's plan
  │
  └─ independent  → primary runs the apply's plan
                    others run their own stored plan,
                    matched on the apply plan's head SHA
  • Member plans are bound to a review round. A non-primary member's plan is looked up by member id and must carry the same head SHA as the apply's plan, so a plan produced for an earlier push of the same pull request is never picked up.
  • A member with no plan for that round fails apply creation. There is no fallback to the apply's plan: it describes a different target's schema, so substituting it would run DDL that target was never planned for. A CLI apply against a multi-target environment fails here by construction, since it has no head SHA to match against — planning happens on a pull request.
  • Operations are keyed by member, not deployment. A member is its deployment and target together, so two targets of one deployment each get their own operation for a given (namespace, shard, table) instead of one target's work being folded into the other's.
  • A single-member apply short-circuits ahead of the contract lookup: it is the plan's own primary, runs the apply's plan either way, and has no sibling whose plan could be substituted — so apply creation does not require database config the trusted control-plane enqueue path is not given.

How it moves us toward the northstar

This is the step A4a's multi-target rejection was waiting on: the apply composer now consumes the full ResolveTargets result, dispatching one operation per member against the plan that member was planned from, instead of collapsing the result to a single route.

Opened by Claude (Fable 5).

aparajon and others added 3 commits September 3, 2026 14:56
An apply whose members are planned together shares one plan, and that is
still the common case. When each member is planned against its own live
schema, though, there is no single plan for the apply to point at.

Add a nullable apply_operations.plan_id so a member can name the plan it
executes, with PlanIDForOperation resolving to the parent apply's plan
when the member has none. An operation with no plan on either row is not
executable and now errors rather than resolving to a row ID no plan has.

TargetOperationKey names the operation key for one target's work when a
single apply addresses several targets, composing with the shard-scoped
key so a sharded target still gets one key per shard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An apply resolves each rollout member to the plan its work is built from
before building operations. Members of an environment whose members hold
the same schema all carry the apply's own plan. A member that was planned
against its own live schema carries its own plan, looked up by member id
and bound to the head SHA the apply's plan was created for, so plans from
an earlier push of the same pull request are never picked up.

A member with no plan for that review round fails apply creation. The
apply's plan describes a different target's schema, so substituting it
would run DDL that target was never planned for.

Operations are grouped by member, and a member is its deployment and
target together, so two targets of one deployment each get their own
operation for a given (namespace, shard, table) rather than sharing one.
An operation names a plan of its own only when it runs a different plan
than its apply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aparajon and others added 4 commits September 3, 2026 16:58
The trusted control-plane enqueue path holds no `Databases` config, so
resolving the member planning contract there fails and apply creation
with it. A single member is the plan's own primary: it runs the apply's
plan under either contract, and no sibling's plan could be substituted
for it, so the contract lookup is unnecessary work at that point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The error identified the operation by its internal numeric row ID, which
is not a triage handle an operator can look up. Name the operation by the
identifiers that route it instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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