Skip to content

feat(gate): force a fresh rebase + CI recheck immediately before merge when base has advanced#2616

Merged
JSONbored merged 2 commits into
mainfrom
feat/gate-fresh-rebase-window
Jul 2, 2026
Merged

feat(gate): force a fresh rebase + CI recheck immediately before merge when base has advanced#2616
JSONbored merged 2 commits into
mainfrom
feat/gate-fresh-rebase-window

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • The update_branch action (which merges the current base into head and would naturally re-trigger CI on a truly-rebased branch) only fired when liveMergeState === 'behind' inside the review-gating pass — not immediately before the merge action itself.
  • The merge decision's own gate treats GitHub's mergeable_state as the sole base-freshness signal, and mergeable_state only detects git-level TEXTUAL conflicts — a base that has advanced with a new, non-conflicting sibling commit (e.g. a second PR's distinct-but-colliding migration file) still reads clean.
  • So a PR sitting green + approved + clean could merge without ever being forced through a fresh rebase-and-CI-recheck cycle against whatever landed on the base moments earlier from a concurrent merge — the base-side half of the same race that mergePullRequest's head-SHA pinning already closes on the PR's own side.

Scope

  • New config-driven gate.requireFreshRebaseWindow setting (minutes, off by default), wired through the full config-as-code chain: migration 0093, Drizzle schema, RepositorySettings type, the DB round-trip in repositories.ts, focus-manifest.ts's gate config parse/serialize/resolve, the OpenAPI schema, and .gittensory.yml.example docs.
  • src/github/backfill.ts — new fetchLiveBaseBranchAdvancedAt: reads the base branch's live tip-commit timestamp via GET /commits/{ref}.
  • src/queue/processors.ts — the base-freshness check lives in runAgentMaintenancePlanAndExecute, immediately before planAgentMaintenanceActions is called with merge-eligible inputs. When the base landed within the configured window, maybeForceFreshRebase forces an update_branch through the SAME action class/write-permission/dry-run/kill-switch stack prReadyForReview's own BEHIND-branch path already uses — not a new action class, just a new trigger condition. A bounded retry cap (3 attempts, keyed per head SHA in the transient cache, mirroring ciPendingDeferStuck's pattern) prevents a fast-moving base from live-locking the PR; past the cap it falls through to a normal merge with an audit trail. Every failure mode (unreadable base commit, cap reached, forced action not authorized) fails open to today's merge behavior.

Safety

  • Off by default — zero behavior change for any repo that hasn't opted in. The new live GitHub read only fires when a repo has explicitly configured the window AND the PR is otherwise merge-mechanically-ready (mergeableClean).
  • No infinite-rebase-loop failure mode: the bounded-retry cap guarantees convergence under a fast-moving base.

Validation

  • npx vitest run test/unit/focus-manifest.test.ts test/unit/data-spine.test.ts test/unit/queue.test.ts — all passing
  • 6 new end-to-end queue.test.ts scenarios (normal merge, forced rebase, off-by-default, bounded-retry fallback, unreadable base commit, unauthorized forced action) — each drives the real processJob path and asserts on audit_events
  • npm run typecheck — clean
  • npm run test:ci — full local gate green
  • npm run test:coverage — 100% branch coverage on all new code
  • npm audit --audit-level=moderate — 0 vulnerabilities

Closes #2552

@dosubot dosubot Bot added the size:L label Jul 2, 2026
@loopover-orb

loopover-orb Bot commented Jul 2, 2026

Copy link
Copy Markdown

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-02 17:07:24 UTC

15 files · 1 AI reviewer · no blockers · readiness 98/100 · CI green · clean

⏸️ Suggested Action - Manual Review

  • Touches a guarded path — held for manual review

Review summary
This PR wires a nullable requireFreshRebaseWindowMinutes setting through schema, migration, API/OpenAPI, manifest parsing, DB persistence, and the merge-time queue path. The core behavior is scoped to configured repos and only runs when the resolved plan contains an immediate merge, then reuses the existing update_branch action pipeline and stops the current pass after a completed force. The visible schema/migration wiring is consistent and the tests cover the default-off path, stale base, recent base, cap, unreadable base, and denied update_branch cases; the main residual risk is freshness precision rather than a visible break.

Nits — 7 non-blocking
  • nit: src/github/backfill.ts:2654 names the value “AdvancedAt”, but the implementation reads the tip commit's committer date, which is only a proxy for when the branch ref moved and can be misleading for direct fast-forwards to older commits.
  • nit: src/queue/processors.ts:2588 keys the retry cap only by repo and PR number, so a cap reached for one base-tip episode suppresses later fresh-rebase attempts for the same PR until the 24h TTL expires.
  • nit: test/unit/queue.test.ts:6124 adds good coverage for forced and capped rebases, but it does not cover GitHub returning the already-up-to-date/update_branch validation path after the PR has incorporated the current base.
  • In src/github/backfill.ts:2654, either rename the helper to reflect that it returns the base tip commit date or add a comment at the call site stating that this is a commit-time proxy, not a true branch-ref update timestamp.
  • In src/queue/processors.ts:2588, consider storing the last forced base ref identity or timestamp alongside the count so the bounded cap can distinguish repeated attempts for the same base tip from a later base advance.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.
  • Touches a guarded path — held for manual review — A maintainer must review and merge this change.
Signal Result Evidence
Code review ✅ No blockers 1 reviewer
Linked issue ✅ Linked #2552
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (size label size:L; 1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 65 registered-repo PR(s), 55 merged, 529 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 65 PR(s), 529 issue(s).
Gate result ⚠️ Not blocking Advisory; not blocking this PR.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository registration is not available in the local Gittensory cache.
  • Public profile languages: not available
  • Official Gittensor activity: 65 PR(s), 529 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • No action.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added gittensor gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. labels Jul 2, 2026
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.03%. Comparing base (8e06d2c) to head (ba41ec6).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2616   +/-   ##
=======================================
  Coverage   96.02%   96.03%           
=======================================
  Files         233      233           
  Lines       26077    26108   +31     
  Branches     9474     9484   +10     
=======================================
+ Hits        25041    25072   +31     
  Misses        425      425           
  Partials      611      611           
Files with missing lines Coverage Δ
src/api/routes.ts 94.73% <ø> (ø)
src/db/repositories.ts 96.54% <ø> (ø)
src/db/schema.ts 69.46% <ø> (ø)
src/github/backfill.ts 96.78% <100.00%> (+<0.01%) ⬆️
src/openapi/schemas.ts 100.00% <ø> (ø)
src/queue/processors.ts 92.28% <100.00%> (+0.09%) ⬆️
src/signals/focus-manifest.ts 99.27% <100.00%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui ecdddee Commit Preview URL

Branch Preview URL
Jul 02 2026, 02:17 PM

A successful forced update_branch produces a new head SHA, which reset a
headSha-keyed counter back to attempt 0 on every force — making the
MAX_FRESH_REBASE_FORCES cap unreachable via the exact path it exists to
bound. Key by repo+PR number only; a 24h TTL still gives an eventual
fresh start.
Two issues found by the gate's own AI review on PR #2616:

1. The freshness check ran before planAgentMaintenanceActions and only
   checked mergeableState === "clean" (a git-level textual-conflict
   signal). A PR with pending CI, a gate blocker, or missing approval
   could still be git-clean, so it could burn the bounded retry cap on
   rebases nobody was about to act on -- exhausting it before the PR was
   ever actually merge-eligible. Moved the check to run after the full
   plan (gate/CI/blockers/precision breakers) resolves, and gated it on
   the plan containing a merge action that would execute now
   (requiresApproval stages for a human, not an immediate merge).

2. maintainerSettingsSchema in src/api/routes.ts omitted
   requireFreshRebaseWindowMinutes, so a dashboard/API PUT for the new
   setting was silently stripped by validation and never reached
   upsertRepositorySettings despite the DB/repository/OpenAPI wiring
   being complete. Added the field plus a round-trip test.
@JSONbored
JSONbored merged commit 7d1014e into main Jul 2, 2026
14 checks passed
@JSONbored
JSONbored deleted the feat/gate-fresh-rebase-window branch July 2, 2026 17:10
@github-actions github-actions Bot mentioned this pull request Jul 2, 2026
12 tasks
JSONbored added a commit that referenced this pull request Jul 2, 2026
The AI review flagged a real defect: cachedFetchLivePullRequestMergeState
/ cachedFetchLivePullRequestState / cachedFetchLivePullRequestHeadSha
each wrote through only the ONE field they cared about, but all three
share a single prStateFetchedAt freshness stamp. A live fetch from any
one of them would make the OTHER two fields look "fresh" to a
subsequent reader despite never having been fetched, so that reader
would silently return undefined for a field that was simply never
populated -- indistinguishable from a confirmed-empty GitHub value.

All three narrow live-fetchers already hit the exact same GET
/pulls/{n} endpoint, just extracting one field each, so there's no
extra API cost to fixing this: a new internal fetchAndCachePrStateFields
helper fetches the full payload once and writes mergeable_state, state,
and headSha through together (headSha omitted, not nulled, when absent,
preserving the existing PARTIAL-UPDATE CONTRACT so a prior headSha the
files cache depends on is never cleared). It also only writes when the
fetch actually succeeds, so a transient failure no longer poisons the
cache with a false "confirmed fresh" stamp the way the old per-field
writes did. The three public, uncached narrow fetchers used by the
act-boundary/gate-override callers are untouched.

Also fixes a second, separate flagged issue: `{ token: "installation-token" }`
in six of this PR's own new test fixtures tripped the deterministic
generic_secret_assignment scanner (a keyword-shaped heuristic, not a
real credential format) -- renamed to `"fake-installation-token"`,
which the scanner's own placeholder-value allowlist already recognizes,
without touching the shared scanner itself.

Rebased onto current main (renumbered migration 0093->0094 to resolve
a collision with #2616, and again to catch up with #2632). Full local
gate (test:ci, npm audit) green; no other changes.
JSONbored added a commit that referenced this pull request Jul 2, 2026
The AI review flagged a real defect: cachedFetchLivePullRequestMergeState
/ cachedFetchLivePullRequestState / cachedFetchLivePullRequestHeadSha
each wrote through only the ONE field they cared about, but all three
share a single prStateFetchedAt freshness stamp. A live fetch from any
one of them would make the OTHER two fields look "fresh" to a
subsequent reader despite never having been fetched, so that reader
would silently return undefined for a field that was simply never
populated -- indistinguishable from a confirmed-empty GitHub value.

All three narrow live-fetchers already hit the exact same GET
/pulls/{n} endpoint, just extracting one field each, so there's no
extra API cost to fixing this: a new internal fetchAndCachePrStateFields
helper fetches the full payload once and writes mergeable_state, state,
and headSha through together (headSha omitted, not nulled, when absent,
preserving the existing PARTIAL-UPDATE CONTRACT so a prior headSha the
files cache depends on is never cleared). It also only writes when the
fetch actually succeeds, so a transient failure no longer poisons the
cache with a false "confirmed fresh" stamp the way the old per-field
writes did. The three public, uncached narrow fetchers used by the
act-boundary/gate-override callers are untouched.

Also fixes a second, separate flagged issue: `{ token: "installation-token" }`
in six of this PR's own new test fixtures tripped the deterministic
generic_secret_assignment scanner (a keyword-shaped heuristic, not a
real credential format) -- renamed to `"fake-installation-token"`,
which the scanner's own placeholder-value allowlist already recognizes,
without touching the shared scanner itself.

Rebased onto current main (renumbered migration 0093->0094 to resolve
a collision with #2616, and again to catch up with #2632). Full local
gate (test:ci, npm audit) green; no other changes.
JSONbored added a commit that referenced this pull request Jul 2, 2026
The AI review flagged a real defect: cachedFetchLivePullRequestMergeState
/ cachedFetchLivePullRequestState / cachedFetchLivePullRequestHeadSha
each wrote through only the ONE field they cared about, but all three
share a single prStateFetchedAt freshness stamp. A live fetch from any
one of them would make the OTHER two fields look "fresh" to a
subsequent reader despite never having been fetched, so that reader
would silently return undefined for a field that was simply never
populated -- indistinguishable from a confirmed-empty GitHub value.

All three narrow live-fetchers already hit the exact same GET
/pulls/{n} endpoint, just extracting one field each, so there's no
extra API cost to fixing this: a new internal fetchAndCachePrStateFields
helper fetches the full payload once and writes mergeable_state, state,
and headSha through together (headSha omitted, not nulled, when absent,
preserving the existing PARTIAL-UPDATE CONTRACT so a prior headSha the
files cache depends on is never cleared). It also only writes when the
fetch actually succeeds, so a transient failure no longer poisons the
cache with a false "confirmed fresh" stamp the way the old per-field
writes did. The three public, uncached narrow fetchers used by the
act-boundary/gate-override callers are untouched.

Also fixes a second, separate flagged issue: `{ token: "installation-token" }`
in six of this PR's own new test fixtures tripped the deterministic
generic_secret_assignment scanner (a keyword-shaped heuristic, not a
real credential format) -- renamed to `"fake-installation-token"`,
which the scanner's own placeholder-value allowlist already recognizes,
without touching the shared scanner itself.

Rebased onto current main (renumbered migration 0093->0094 to resolve
a collision with #2616, and again to catch up with #2632). Full local
gate (test:ci, npm audit) green; no other changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(gate): force a fresh rebase + CI recheck immediately before merge when base has advanced

1 participant