Skip to content

feat(github): downgrade auto-apply when the schema dir changed on base#737

Closed
Kiran01bm wants to merge 5 commits into
mainfrom
kiran01bm/require-up-to-date-with-base-gate
Closed

feat(github): downgrade auto-apply when the schema dir changed on base#737
Kiran01bm wants to merge 5 commits into
mainfrom
kiran01bm/require-up-to-date-with-base-gate

Conversation

@Kiran01bm

@Kiran01bm Kiran01bm commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a default-on, path-scoped "up-to-date-with-base" gate. On the automatic apply path, if the resolved schema directory changed on the PR's base branch since the branch diverged, SchemaBot downgrades to manual confirmation instead of applying against a base that has moved underneath the plan. A PR can be thousands of commits behind and still auto-apply, as long as its schema subtree is unchanged.

What

  • New GitHub primitive SchemaDirChangedOnBase: given the base ref name, it compares baseRef...head, resolves the live base tip from the compare response's BaseCommit (not the PR's pull.base.sha snapshot, which is typically the base tip at PR creation and would defeat the gate), then compares the git tree SHA of the resolved schema directory at the merge base vs the live base tip. Equal tree SHAs prove an identical subtree; a difference (or the directory being added/removed) means the base changed it. Path resolution walks one level at a time with shallow tree fetches, so it is truncation-safe and fails closed.
  • Gate wired into the automatic apply path (handleApplyCommand) only. apply-confirm bypasses it because it goes through a different handler (not because of a nil stored plan). On downgrade the lock stays held so the operator can review and confirm.
  • Downgrade comment names the base files that changed (capped, with an "…and N more" overflow) and tells the operator to update the branch or run apply-confirm.
  • Downgrade metric schemabot.auto_apply.downgraded.total with a reason label (base_drift, base_drift_unverified, plan_load_failed) so a GitHub API degradation that downgrades applies fleet-wide is visible, not silent.
  • Config knob require_up_to_date_with_base: global default plus a per-repository override. Precedence is repo override → global → default true. High-churn repositories can opt out.
  • Fail-closed: any uncertainty (compare/tree API error, missing merge base, missing base commit) downgrades to manual confirmation rather than auto-applying against an unverified base.

Why

Automatic apply re-plans against the PR head, but nothing checked whether the base branch had changed the schema directory since the branch diverged. In fast-moving repositories a plan could be auto-applied against a base that already moved, silently applying against a stale view. Whole-branch "commits behind" is unusable when the base changes constantly, so the gate is scoped to the schema subtree: only a change under the resolved schema root matters.

Before / after

Before: auto-apply

re-plan vs stored auto-plan (DDL drift) ── differ ─▶ manual confirm
└─ same ──▶ APPLY   ◀── base may have moved under the schema dir, unseen

After: auto-apply

compare(baseRef…head) ─▶ merge base + live base tip
merge base == live base tip ............... APPLY (base did not advance)
schema-dir tree(mergeBase) == tree(tip) ... APPLY (base moved elsewhere only)
schema-dir tree differs / added / removed ─▶ manual confirm (names files)
error / unknown ........................... manual confirm (fail closed)
(apply-confirm bypasses this gate)

Known gaps (intentionally out of scope — do not flag as new issues)

These fall back to prior behavior (no gate) rather than regressing anything. The code comment on SchemaDirChangedOnBase records the two symlink gaps; the rest are documented in the design doc:

  • Namespace symlinks whose targets live outside the schema directory: a tree SHA hashes the symlink blob, not the target's contents. (In code comment.)
  • A base branch repointing the environment-root symlink itself. (In code comment.)
  • A PR relocating its schema root while the base changes the old path.
  • Check-to-queue TOCTOU: the gate is a best-effort downgrade, not a hard guarantee the base is current at execution time.
  • POST /api/apply bypasses this gate (operator-authed direct plan execution); pre-existing, by-design entry-point asymmetry.

Deferred follow-ups (tracked — do not flag as new issues)

Tracked in the design doc's follow-ups (personal-kmuddukrishna: design-stuff/schemabot-apply-base-drift-gate/follow-ups.md):

  • BD-1 Root-config (schemaPath == ".") makes the gate whole-repo sensitive; needs a changed-file-list path or documented degradation.
  • BD-2 Collapse the gate's API calls with a single reversed head...base compare and dedup treeSHAForPath against fetchGitSubtreeCached.
  • BD-3 Unify the DDL-drift downgrade onto postAutoApplyDowngrade so it also records the action_required check and refreshes the aggregate (today it does neither).

Add a default-on, path-scoped up-to-date-with-base gate. On the automatic
apply path, compare the resolved schema directory's tree SHA at the PR's
merge base against the current base; if it changed (or can't be verified),
downgrade to manual confirmation instead of auto-applying against an
advanced base. Repos can opt out via require_up_to_date_with_base;
apply-confirm remains the deliberate override.
Teach the webhook test harness to serve faithful single-level git trees for
non-recursive fetches so the base-branch drift gate's per-level path walk can
be exercised, and add an integration test asserting that a schema change on the
base branch downgrades automatic apply to manual confirmation.
Copilot AI review requested due to automatic review settings July 16, 2026 06:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a default-on “up-to-date-with-base (schema subtree)” safety gate to the automatic apply path. If the resolved schema directory’s tree differs between the PR’s merge-base and the current base, SchemaBot downgrades from auto-apply to manual confirmation (apply-confirm can still override), avoiding auto-applying against a base that has moved under the plan.

Changes:

  • Add GitHub primitive to compare schema-directory tree SHAs at merge-base vs current base and (best-effort) list changed schema files on base.
  • Wire the gate into apply (auto-confirm) flow with fail-closed downgrade messaging and shared downgrade handling.
  • Add config knobs (global + per-repo override) and comprehensive unit/integration coverage, including updates to the fake GitHub tree server for shallow tree walks.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/webhook/webhook_integration_test.go Extends fake GitHub server to support shallow tree fetches + compare responses needed by the new base-drift gate.
pkg/webhook/base_drift_downgrade.go Adds operator-facing downgrade reason builders (including capped file listing).
pkg/webhook/base_drift_downgrade_test.go Unit tests for downgrade messaging behavior and file-list capping.
pkg/webhook/apply_handlers.go Adds the base-drift gate to the automatic apply path and factors a shared downgrade tail helper.
pkg/webhook/apply_base_drift_integration_test.go New integration test ensuring auto-apply downgrades when base schema subtree changed and records an action_required check.
pkg/github/base_drift.go Implements SchemaDirChangedOnBase via merge-base resolution + shallow tree SHA comparison and filtered changed-file listing.
pkg/github/base_drift_test.go Unit tests for base-drift detection and path filtering semantics.
pkg/api/config.go Adds global + per-repo require_up_to_date_with_base config and resolution logic (default true).
pkg/api/config_test.go Tests precedence and defaulting behavior for RequiresUpToDateWithBase.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/webhook/apply_base_drift_integration_test.go Outdated
Comment thread pkg/webhook/apply_base_drift_integration_test.go Outdated
Kiran01bm and others added 3 commits July 16, 2026 21:32
…de metric

The gate compared the schema subtree against GitHub's pull.base.sha snapshot,
which for most PRs equals the merge base and silently short-circuits as
"no drift" — exactly in the fast-moving-base case the gate exists for. Resolve
the live base tip from the compare response instead, and count auto-apply
downgrades so a GitHub API degradation is visible rather than silent.
@Kiran01bm Kiran01bm closed this Jul 19, 2026
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.

2 participants