Skip to content

feat(ab-3238): integrate origin/<target> drift before opening feature PR - #471

Merged
PolyphonyRequiem merged 1 commit into
mainfrom
feat/ab-3238-integrate-target-drift
May 19, 2026
Merged

feat(ab-3238): integrate origin/<target> drift before opening feature PR#471
PolyphonyRequiem merged 1 commit into
mainfrom
feat/ab-3238-integrate-target-drift

Conversation

@PolyphonyRequiem

Copy link
Copy Markdown
Owner

Closes AB#3238.

Problem

strip_planning_artifacts (apex-driver) pushed the feature branch and triggered feature-PR creation against main without first integrating drift from origin/main. If main had advanced since the apex was dispatched, the feature PR's diff included that drift, presenting unrelated changes as if the feature was "removing" them. LLM reviewer flagged them; pr_fixer "fixed" by recreating; AB#3236's revise_counter loop kept it spinning.

Live repro: apex 62286666 dogfood (2026-05-18) — main advanced 8 commits while the apex ran; USSec/USNat config files showed as missing from the feature branch; fixer iter 1 recreated them; iters 2-7 each concluded "already fixed" without committing; loop never terminated.

Fix

New integrate_target_drift node at the entry point of feature-pr.yaml. It runs once per feature PR (covering apex-driver → main AND apex-item-dispatch → feature/; both GitHub and ADO legs). Strategy: git merge --no-ff origin/<target> on the feature branch + push. Merge (not rebase) preserves upstream MG → feature merge-commit SHAs, avoids force-push, and leaves the drift integration visible in PR review.

On conflict: script aborts cleanly, captures conflicted_files, surfaces merge_conflict to a human gate. Operator resolves by hand, pushes, clicks Retry; script re-checks behind_by and falls through if now in sync.

Pairs with AB#3236

AB#3236 (already merged) caps the runaway. This PR prevents the runaway from being triggered in the first place. The two together address the dogfood failure mode end-to-end.

Tests

  • 5 script-level Pester tests against a synthetic two-clone fixture: no drift, clean drift (merge + push), conflict (worktree clean after abort, conflicted_files captured), feature_branch_diverged, envelope contract.
  • 6 new lint mutation tests + valid-fixture updated to include integrate_target_drift as entry_point.
  • lint-strict-undefined (15 workflows) ✓
  • lint-apex-driver
  • lint-feature-pr clean for AB#3238 checks (1 pre-existing unrelated missing-ado-remediation-route failure unchanged from main; tracked separately).

Rubber-duck pass adopted

  • Place at feature-pr boundary (single chokepoint) rather than apex-driver only.
  • Drift script owns its push (don't depend on strip pushing).
  • Removed Skip option from gates (would reproduce the bug).
  • Guard git merge --abort with MERGE_HEAD check.
  • Added distinct feature_branch_diverged error path.

Adds a new entry-point node to feature-pr.yaml that detects and resolves
drift between the feature branch and its target before the PR is opened.
Drift in this context = commits that landed on origin/<target_branch>
after feature/<id> was forked from it. Without integration, the feature
PR diff shows those drift commits as "removals" from the feature branch,
fooling LLM reviewers into flagging unrelated files (compounding with
AB#3236's revise_counter loop to burn unbounded LLM tokens).

Why this matters now:

Apex 62286666's most recent dogfood run (2026-05-18) hit exactly this:
main advanced 8 commits while the apex was running (USSec/USNat config
landed in #15615238). The feature PR opened with those 8 commits' worth
of files presented as "missing" from the feature branch. pr_initial_reviewer
flagged them as drift. pr_fixer iter 1 dutifully recreated them. Iters
2-7 each concluded "already fixed" without committing — with AB#3236's
revise_counter stuck at 1, the loop never terminated.

AB#3236 caps the runaway (no-commit fast-fail). This fix prevents the
runaway from being triggered in the first place.

What changed:

* New script .conductor/registry/scripts/integrate-target-drift.ps1.
  - Fetches origin/<target> and origin/<feature>.
  - Refuses to proceed if local <feature> has diverged from
    origin/<feature> (both sides have unique commits).
  - If behind_by > 0: git merge --no-ff origin/<target> and pushes.
  - On conflict: aborts cleanly, captures conflicted_files, surfaces
    merge_conflict for the gate to handle.
  - Routing-style envelope (always exits 0; error_code drives routes).

* feature-pr.yaml: integrate_target_drift is now the entry_point.
  Routes: merge_conflict -> conflict gate; other error -> failed gate;
  success -> pr_platform_router (existing flow continues unchanged).

* Two new human gates:
  - integrate_target_drift_conflict_gate (retry / abort).
  - integrate_target_drift_failed_gate (retry / abort).
  Retry semantics: operator resolves by hand (cd into worktree, merge,
  push), then clicks Retry; the script re-checks behind_by and falls
  through to PR creator if now in sync.

  No "skip" option intentionally — skipping reproduces the bug.

* Merge (not rebase) chosen to preserve SHAs of upstream MG -> feature
  merge commits, avoid --force-with-lease, and leave an obvious
  "drift integration" commit visible in PR review.

Lint additions (lint-feature-pr.ps1):

* missing-drift-integrator        - integrate_target_drift node exists
* drift-integrator-not-entry      - entry_point is integrate_target_drift
* missing-drift-gate              - both gates exist
* drift-routes-malformed          - routes branch on error_code correctly

Tests:

* integrate-target-drift.Tests.ps1 — 5 script-level Pester tests against
  a synthetic two-clone git fixture: no drift, clean drift, merge
  conflict (worktree clean after abort, conflicted_files captured),
  feature_branch_diverged, envelope contract.
* lint-feature-pr.Tests.ps1 — 6 new mutation tests + valid-fixture
  updated to include integrate_target_drift as entry point.

Scope:

The fix lives in feature-pr.yaml because it's the single chokepoint
through which every feature PR flows (apex-driver -> feature-pr.yaml
for apex->main; apex-item-dispatch -> feature-pr.yaml for
child->feature/<apex>). Both GitHub and ADO platform legs are
protected by the same entry node.

Rubber-duck pass adopted: feature-pr boundary placement, drift script
owns its push (not strip), removed skip option (would reproduce bug),
guarded merge --abort with MERGE_HEAD check, added
feature_branch_diverged distinct error path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PolyphonyRequiem
PolyphonyRequiem merged commit 4c2b5ff into main May 19, 2026
1 check failed
@PolyphonyRequiem
PolyphonyRequiem deleted the feat/ab-3238-integrate-target-drift branch May 19, 2026 06:24
PolyphonyRequiem added a commit that referenced this pull request May 19, 2026
PR #471 shipped AB#3238 with git merge --no-ff origin/<target> as the
drift-integration strategy. User feedback: clean linear feature-PR
history is preferred over preserving the upstream MG-merge SHA chain;
the extra integration merge commit polluted the PR's Commits tab.

This change:

* Script (integrate-target-drift.ps1): replaces git merge --no-ff  with git rebase origin/<target>, replaces git push origin <feature>  with git push --force-with-lease=<feature>:<expected-sha> (the  expected SHA is captured BEFORE rebase to defeat lease-weakening from  background fetches).
* Conflict path: replaces git merge --abort (guarded by MERGE_HEAD)  with git rebase --abort (guarded by Test-RebaseInProgress, which  checks both 
ebase-merge and 
ebase-apply dirs via  git rev-parse --git-path).
* New pre-flight: Test-WorktreeBlocked refuses to proceed if any  in-progress merge/rebase/cherry-pick/revert OR uncommitted changes  are detected, surfaced as new error_code worktree_dirty.
* New envelope fields: strategy: 'rebase', old_head_sha,
ew_head_sha.  Dropped merge_commit_sha (rebase has no single integration SHA).
* Edge case: rebase-no-op (HEAD didn't move despite behind_by>0) exits  success without force-push.
* Initial-push path: if origin/<feature> doesn't exist yet, falls back  to git push --set-upstream (no lease — nothing to overwrite).
* error_code value merge_conflict retained for workflow-route  compatibility (semantic meaning is still 'drift integration  conflicted'); only the operator-instructions in the conflict gate  prompt change (rebase + force-with-lease, not merge + plain push).
* Workflow YAML: version 2.4.9 -> 2.4.10. Updates agent comment,  conflict-gate operator instructions (full rebase/continue/force-push  recipe with explicit git rev-parse refs/remotes/origin/<feature> for  the lease), failed-gate prompt (documents new worktree_dirty and  updated push_failed semantics).
* Tests: clean-drift test asserts strategy='rebase', new_head_sha != old_head_sha, and verifies the force-pushed SHA landed on origin/<feature>. Conflict test asserts no rebase-merge or rebase-apply dir remains after abort. New worktree_dirty test. Envelope-contract test updated for new field set. 6/6 pass.

Lint (lint-feature-pr.ps1) contract unchanged — routes still check  �rror_code == 'merge_conflict', gate names + entry-point invariant  preserved. 28/29 lint tests pass (1 pre-existing missing-ado-remediation-route failure unrelated to this change).

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PolyphonyRequiem added a commit that referenced this pull request May 19, 2026
4 root causes traced from PR #474/#475 admin-merges:

1. Console.Out parallel-test race in StringBoolArgTests (caused
   the 2 net11.0 xUnit failures: Parse_RejectsAnythingElse('no')
   and NextReady_TwigShowThrows). Take ConsoleTestLock.AsyncLock
   like every other Console-redirecting test class does.
2. Install Pester/Install powershell-yaml lacked if: always() —
   so when (1) failed dotnet test, modules never installed and
   if: always() lints false-positive'd 'module required'. Add
   if: always() to both install steps.
3. lint-version-drift red on main: feature-pr.yaml (2.4.10) and
   implement-merge-group.yaml (2.4.9) drifted from index.yaml
   (2.4.8). PRs #471 and #469 bumped one workflow each without
   cutting a tag or bumping siblings — per
   docs/decisions/versioning-strategy.md, bumps happen at
   release-cut time, not PR-merge time. Roll both back to 2.4.8.
4. lint-primary-completer-trust-chain + lint-pwsh-jinja-bareword
   failures were pure consequences of (2). Resolved transitively.

Verified: dotnet test --filter 'Category!=Slow&Category!=Integration'
Passed: 3638, Failed: 0, Skipped: 4. lint-version-drift PASS for all
15 workflow YAMLs at 2.4.8.

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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