Skip to content

feat(apply): dual-write apply_operations row on apply create#214

Open
Kiran01bm wants to merge 1 commit into
kiran01bm/multi-deployment-config-resolverfrom
kiran01bm/multi-deployment-apply-dual-write
Open

feat(apply): dual-write apply_operations row on apply create#214
Kiran01bm wants to merge 1 commit into
kiran01bm/multi-deployment-config-resolverfrom
kiran01bm/multi-deployment-apply-dual-write

Conversation

@Kiran01bm
Copy link
Copy Markdown
Collaborator

@Kiran01bm Kiran01bm commented Jun 2, 2026

Stacked on #213 — review and land that one first. Re-target to main once #213 merges.

Apply create now writes one apply_operations row alongside the applies row and its initial tasks, all in a single transaction. Puts the per-deployment data shape in place ahead of the operator claim-loop change that consumes it. No behavior change at apply time today — the claim loop still drives off applies rows.

Table of contents

Storage API

// New — atomic apply + tasks + operations in one transaction.
ApplyStore.CreateWithTasksAndOperations(ctx, apply, tasks, operations) (int64, error)

CreateWithTasks now delegates to it with nil operations, so the lock + transaction code lives in one place. insertApplyOperation(ctx, exec, op) is extracted so the in-tx caller and the existing public ApplyOperationStore.Insert share the same insert + dup-key translation.

Service wiring

createStoredApply builds one ApplyOperation mirroring the apply's (Deployment, Target) and passes it through CreateWithTasksAndOperations. The config layer still hard-blocks deployments: maps with >1 entries, so this slice is always length 1 today.

Why not call ResolveDatabaseTargets at apply time

The plan already carries the resolved (deployment, target) pair from plan-time ResolveDatabaseTarget. Re-resolving at apply time would silently change routing if server config drifted between plan and apply. The next PR (which makes plans deployment-agnostic for multi-deployment configs) is the right place to wire ResolveDatabaseTargets at apply time.

Tests

  • TestExecuteApplyQueuesRemoteApplyForScheduler now also asserts the dual-written operation (deployment / target / state=pending).
  • New integration tests in pkg/storage/mysqlstore/applies_test.go:
    • CreateWithTasksAndOperationsCommitsAtomically — apply + task + operation all visible after commit; ApplyID back-filled on the caller-supplied operation struct.
    • CreateWithTasksAndOperationsRollsBackOnOperationFailure — a duplicate (apply_id, deployment) on the second operation insert rolls back the whole transaction, leaving no orphan apply.

Verification

  • go build ./...
  • go vet ./...
  • go vet -tags integration ./pkg/storage/mysqlstore/...
  • go test ./pkg/api/... ./pkg/state/... ./pkg/storage/... ./pkg/schema/... ./pkg/webhook/... ./pkg/tern/...

Out of scope

  • Operator claim loop reading apply_operations rows.
  • Lifting the config-layer hard-block on multi-entry deployments: maps.
  • Per-deployment progress comments / Check Run rollup.

Rollback

Pure revert. New apply_operations rows written during soak are harmless — no reader yet — and can be left in place or deleted with a one-shot query.

ApplyService.createStoredApply now writes one apply_operations row
alongside the applies row and its initial tasks, all in a single
transaction. This puts the per-deployment data shape in place ahead of
the operator claim-loop change that consumes it.

Storage layer:
- Add ApplyStore.CreateWithTasksAndOperations(ctx, apply, tasks,
  operations) — the new atomic apply-create path.
- Refactor CreateWithTasks to delegate with nil operations so all the
  lock + transaction code lives in one place.
- Extract insertApplyOperation(ctx, exec, op) so the in-tx caller and
  the existing ApplyOperationStore.Insert share the same insert + dup-key
  translation (ErrApplyOperationExists on the (apply_id, deployment)
  unique constraint).

Service layer:
- createStoredApply builds one ApplyOperation mirroring the apply's
  (Deployment, Target) and passes it through CreateWithTasksAndOperations.
- The plan already carries the resolved (deployment, target) pair from
  plan-time ResolveDatabaseTarget, so we do not re-resolve at apply time;
  re-resolution would silently change routing if config drifted between
  plan and apply. The next PR (which makes plans deployment-agnostic for
  multi-deployment configs) is the right place to wire ResolveDatabaseTargets
  at apply time.
- The config layer still hard-blocks deployments maps with >1 entries,
  so this slice is always length 1 today.

Tests:
- TestExecuteApplyQueuesRemoteApplyForScheduler now also asserts the
  dual-written operation (Deployment / Target / State=pending).
- New integration tests in pkg/storage/mysqlstore/applies_test.go:
  - CreateWithTasksAndOperationsCommitsAtomically — apply + task +
    operation are all visible after commit; ApplyID is back-filled on
    the caller-supplied operation struct.
  - CreateWithTasksAndOperationsRollsBackOnOperationFailure — a duplicate
    (apply_id, deployment) on the second operation insert rolls back the
    whole transaction, leaving no orphan apply row.

Verification:
- go build ./...
- go vet ./...
- go vet -tags integration ./pkg/storage/mysqlstore/...
- go test ./pkg/api/... ./pkg/state/... ./pkg/storage/... ./pkg/schema/...
  ./pkg/webhook/... ./pkg/tern/...
@Kiran01bm Kiran01bm marked this pull request as ready for review June 2, 2026 10:06
@Kiran01bm Kiran01bm requested review from aparajon and morgo as code owners June 2, 2026 10:06
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ea1da1cef3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pkg/api/plan_handlers.go
Comment on lines +601 to +602
// routing. The data shape is what the operator claim-loop PR consumes
// once that gate is lifted and plans become deployment-agnostic.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove future-PR commentary from the code comment

/workspace/schemabot/AGENTS.md says not to add comments describing what changed and not to reference PRs in code comments. In this new comment, the behavior is explained in terms of the current gate and an “operator claim-loop PR” that will consume the data later; that will go stale and violates the repository guidance. Please rewrite this as a stable invariant for why exactly one operation is written here today, or drop the future-work details.

Useful? React with 👍 / 👎.

Comment thread pkg/api/handlers_test.go
Comment on lines +1050 to +1051
// (deployment, target). Today's hard-block keeps this at one row; the
// operator claim-loop PR is what consumes them.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove future-PR commentary from the test comment

/workspace/schemabot/AGENTS.md says comments should not describe temporary/current implementation state or reference PRs. This test comment bakes in “Today's hard-block” and “operator claim-loop PR” context, so it will become misleading once the next apply path lands. Please rephrase it to describe the durable behavior being asserted, or omit the explanatory comment.

Useful? React with 👍 / 👎.

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