feat(storage): durable merge gate requests for schema-mutating applies - #867
feat(storage): durable merge gate requests for schema-mutating applies#867aparajon wants to merge 4 commits into
Conversation
…lies An apply reaching terminal success changes its target's live schema, which stales the stored plan check state of every other open PR planning against that target. This adds the storage layer the refresh guardrail is built on: a check_refresh_requests outbox (one idempotent row per apply, claimed under a rotating lease with bounded attempts, heartbeat, coalescing of same-target pending rows, a sweep join over completed applies missing a request, and a stuck-processing terminator), plus the check-store reverse index and the conditional fail-closed flip (head-SHA guarded, in-flight-apply guarded) that the fan-out will use. The integration job budget grows to fit the added container-backed tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Introduces the storage-layer foundation for durable “check refresh” requests, enabling at-least-once re-planning of stored check state for sibling PRs whenever an apply successfully mutates a target’s live schema.
Changes:
- Added a durable
check_refresh_requestsoutbox/table + MySQL store implementing claim/lease/heartbeat/completion/failure semantics with coalescing and backfill sweep support. - Extended stored check state with a target-wide reverse index (
GetByTarget) and a head-SHA-conditional, fail-closed flip for refresh failures (MarkBlockedForFailedRefresh). - Added integration tests for the new store and adjusted CI timeout to accommodate the expanded integration suite.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/storage/types.go | Defines check refresh request types, states, and attempt budget constant. |
| pkg/storage/storage.go | Extends storage interfaces with CheckRefreshRequestStore and new check-store methods. |
| pkg/storage/mysqlstore/storage.go | Wires the new check refresh request store into the MySQL storage implementation. |
| pkg/storage/mysqlstore/checks.go | Implements GetByTarget and MarkBlockedForFailedRefresh in the MySQL check store. |
| pkg/storage/mysqlstore/check_refresh_requests.go | Implements the durable check refresh request MySQL store (record/claim/lease/heartbeat/complete/fail/sweep). |
| pkg/storage/mysqlstore/check_refresh_requests_test.go | Adds integration tests covering request lifecycle semantics and the new check-store APIs. |
| pkg/storage/errors.go | Adds store-level sentinel errors for not-found and lease-loss cases. |
| pkg/schema/mysql/checks.sql | Adds idx_env_db to support target-wide check lookups. |
| pkg/schema/mysql/check_refresh_requests.sql | Adds the check_refresh_requests table and supporting indexes. |
| pkg/api/handlers_test.go | Updates storage mock to satisfy the expanded Storage interface. |
| .github/workflows/test.yaml | Increases workflow timeout to fit the expanded integration test runtime. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ion fails The operator terminalizes a task-less operation and re-derives the parent apply's state as two separate writes, so the parent briefly reads running after the operation is already failed. Poll for the derived state instead of asserting it in the same instant the operation turns failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The durable fan-out table is named for what it actuates — sibling changes' merge gates — rather than the GitHub Checks vocabulary. The originating change identity is now code-host neutral: provider (default github) plus a provider-scoped change_key string replace the GitHub-shaped pull_request integer, so changes on other code hosts can originate applies without a schema change. WebhookProviderGitHub generalizes to ProviderGitHub, shared by every table that attributes rows to a code host. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4a57268 to
5bce323
Compare
|
🤖 Review findings - created by Kiran's code review agent - for pull/867, 5bce323. Verdict: 9 findings — 1 blocking (rebase-and-port onto main's sqlstore layout + full CI), 6 non-blocking, 2 suggestions. Blocking
Non-blocking
General suggestions
The one thing that could have broken, verifiedThe lease state machine in merge_gate_requests.go — the interaction of Verified correct
This review was generated by Claude Code (claude-fable-5). |
Why this matters: When an apply reaches terminal success, the target database's live schema has changed — and every other open PR that planned against that target is now holding stale check state. A stale green check on a tier-0 safety gate is the failure mode this workstream closes. This PR is the storage foundation of the merge gate guardrail (stack 1/7).
What it does:
merge_gate_requeststable and store — a durable outbox mirroring thewebhook_eventslease pattern: one idempotent row per apply (Record), claimed under a rotating lease with bounded attempts (ClaimNextviaFOR UPDATE SKIP LOCKED),Heartbeat, lease-token-conditionalMarkCompleted/MarkFailed, same-target coalescing of pending rows (CompletePendingCoalesced), a sweep join that backfills completed applies missing a request (FindCompletedAppliesMissingRequest), and a stuck-processing terminator.provider,repository,change_key(a PR number rendered as a string on GitHub; other providers use their own change handle) — so the core storage layer never assumes GitHub.GetByTarget(environment, database type, database) backed by a newidx_env_db, andMarkBlockedForFailedRefresh— a conditional, fail-closed flip that only lands when the stored head SHA is still current and no apply is in flight.How it moves us toward the northstar: Declarative schema GitOps is only safe if stored check state always reflects the live target. A durable, at-least-once merge gate outbox means no schema mutation — GitHub-driven or CLI-driven — can leave a sibling PR holding a stale verdict.
The chain: #867 (storage) → #868 (drive-tail recording) → #866 (settle re-plan processor) → #939 (request kinds + hold storage) → #940 (preflight hold fan-out) → #941 (apply-start gate) → #942 (plan-time holds). Merges bottom-up; each PR retargets to
mainas its base merges.🤖 Generated with Claude Code