Skip to content

feat(api): record durable merge gate requests at apply drive tails - #868

Draft
aparajon wants to merge 4 commits into
armand/check-refresh-storagefrom
armand/check-refresh-drive-tail
Draft

feat(api): record durable merge gate requests at apply drive tails#868
aparajon wants to merge 4 commits into
armand/check-refresh-storagefrom
armand/check-refresh-drive-tail

Conversation

@aparajon

@aparajon aparajon commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Why this matters: The moment a drive settles an apply to terminal success, every other open PR planning against that target holds check state computed against a schema that no longer exists. If that moment isn't captured durably, the merge gate guardrail has nothing to act on — and a pod crash right after the terminal write would lose the signal forever. Stack 2/7, on top of #867.

What it does:

  • recordMergeGateIfApplyResolved runs at all three operator drive tails (recovery, multi-operation drive, pending-stop recovery), placed before pending control requests are completed — the same ordering the terminal summary uses. Only terminal success records; rollbacks are ordinary apply rows settling to Completed, so they're covered with zero special-casing.
  • Gated on a merge gate consumer existing. A gRPC/CLI-only server (no GitHub runtime) has no PR check state to refresh and no processor to drain requests, so recording there would leave rows pending forever. The webhook handler's registration of OnMergeGateRecorded doubles as the consumer signal: drive tails record only when it is set — a nil check, so the drive tail pays nothing for it.
  • OnMergeGateRecorded also serves as the wake-up: the drive tail invokes it after a successful record so a co-located merge gate processor drains immediately instead of waiting out its poll interval. The durable row stays the source of truth: a lost wake-up only costs poll latency, never the re-plan.
  • Recording never fails the drive: a storage error is logged with the apply's triage attributes, counted (RecordMergeGateRecordFailure), and left for the processor's backstop sweep to re-record. A sustained source="sweep" rate on RecordMergeGateRecorded is the operator signal that drive tails are failing to record.
  • Tests: the integration test drives a real apply through the webhook command path to terminal success and asserts the pending merge gate request is durably visible with full target + attribution fields and that the notifier fires; a unit test pins both sides of the consumer gate.
drive tail (apply → Completed)
    ├─ 0. consumer registered?           (nil ⇒ no GitHub runtime ⇒ skip)
    ├─ 1. record merge_gate_request      (durable, idempotent per apply)
    ├─ 2. kick the merge gate processor  (in-memory hint, loss-tolerant)
    └─ 3. complete pending control requests / terminal summary

How it moves us toward the northstar: Every schema mutation on a GitHub-connected server — PR-driven or CLI-driven — leaves a durable, at-least-once breadcrumb the moment it lands, and servers with no PR surface stay entirely out of the flow. The processor (stack 3/7, #866) turns those breadcrumbs into re-planned sibling PR checks.

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 main as its base merges.

🤖 Generated with Claude Code

aparajon and others added 4 commits August 5, 2026 11:13
When a drive settles an apply to terminal success, the target's live schema
has changed and other open PRs' stored check state against that target is
stale. The operator drive tails now record a durable check refresh request
(idempotent per apply) as part of the terminal transition, before pending
control requests are completed — the same ordering the terminal summary uses.
Recording never fails the drive: a storage error is logged and counted, and
the processor's backstop sweep re-records it. Rollbacks need no special
casing — a rollback is an ordinary apply row settling to Completed, so the
same tails cover it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…heck refresh

The refresh processor polls the durable request table, so a request recorded
right after a tick waits a full poll interval before any sibling PR check
moves. OnCheckRefreshRecorded lets the webhook handler register a wake-up
that the drive tail invokes after a successful record; the durable row stays
the source of truth, so a missed call (processor on another pod, callback
unset) only costs poll latency, never the refresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…time

A gRPC/CLI-only server has no PR check state to refresh and no processor to
drain refresh requests, so a drive tail recording there would leave rows
pending forever. The webhook handler's registration of the recorded-notifier
now doubles as the consumer signal: drive tails record (and wake the
consumer) only when it is set, and skip with a debug log otherwise. The
check is a nil test, so the drive tail pays nothing for it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…requests

The drive tail records the originating change as a provider-scoped
change_key string via ChangeKeyForPullRequest, replacing the pull_request
integer, and component prose follows the merge gate rename.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aparajon
aparajon force-pushed the armand/check-refresh-drive-tail branch from 6ba60ea to 56faf88 Compare August 5, 2026 15:14
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