Skip to content

Range derivation verified against real merges — rebase done, squash and merge-commit pending #135

Description

@sameera

As a delivery lead, I want the close-time range: verified against real merges of each
strategy, so that a distillation run weeks later recomputes the diff the PR actually contained.

Re-scoped 2026-07-25. Originally this ran against a provisioned scratch repo. It does not need
to: range derivation needs a real merged PR, not a disposable one, and this repo has a dozen.
The rebase-merge path, post-branch-delete head reachability, and the authoritative-set
verification loop were measured read-only against sameera/nexus's own history and recorded as
Run 2026-07-25 — 6/6
PASS, zero divergences. What remains is the two merge strategies this repo has never used.
Sibling stories #134 and #136 were closed as not-worth-the-scope-grant; see their closing comments.

Acceptance Criteria

  • Given a multi-commit PR merged by rebase, when range derivation runs, then the
    derived base reproduces the PR's authoritative changed-file set
    (baseRefOid...pull/<N>/head, queue paths excluded). — 6/6 PASS, recorded.
  • Given a merged PR whose head branch has been deleted from the remote, when
    pull/<N>/head is fetched, then the PR head is still reachable and the verification path
    can run. — 4/4 PASS, recorded.
  • Given a multi-commit PR merged by squash, when range derivation runs against
    it, then the derived base reproduces the authoritative changed-file set, and the result is
    appended to the acceptance record against the libs/pr-worktree commit that produced it.
  • Given a real PR merged with a merge commit, when range derivation runs against it,
    then base equals mergeCommit^1, the file sets are equal, and the result is appended to
    the record.
  • Given a single-commit PR merged by rebase, when the range is derived, then
    base equals mergeCommit^1, base is an ancestor of head, and the three-dot diff is
    non-empty. — 2/2 PASS (distill: #220 the body cap measures the concept, not its neighbours #236, distill: Configurable Worktree Location for the --pr Flow (#178) #191), recorded as Run 2026-08-06; distill: Configurable Worktree Location for the --pr Flow (#178) #191 also extends
    post-branch-delete head reachability to the single-commit path.
  • Given all three remaining strategies have been exercised, when the record is read,
    then each carries its derived SHAs, file-set equality result, and any diagnostic — and
    divergences from the injected-runner tests are filed or recorded as zero.

Strategy assignment

This story is not sit-down-and-finish work. It is gated on real PRs being merged with a chosen
strategy. Claim each slot below against a named PR as it comes up, then run the check and append to
the record. When all three are filled, this closes and #132 can close with it.

Slot Requires Claimed by Done
squash-of-N multi-commit PR, squash merge next real epic branch [ ]
merge commit any PR, merge-commit merge #237 (merge it with the merge-commit button) [ ]
single-commit rebase 1-commit PR, rebase merge #236 and #191 (Run 2026-08-06) [x]

Why the commit count matters. range.ts:84 branches on parentCount >= 2 || pr.commitCount <= 1.
A single-commit PR takes the unambiguous path regardless of whether it was squashed or rebased — so
squash-merging a one-commit PR proves nothing about squash-of-N. Only a multi-commit PR reaches
the ambiguous branch this story exists to verify. Check the commit count before claiming a slot.

This is not contriving. The refuted alternative in the decision record was a local git
simulation
, where PR metadata and post-branch-delete reachability would be fabricated by the same
beliefs under test. A real merge of a real PR on the real repo has neither defect — choosing which
button to press is not fabricating a condition.

How to run it

No scratch repo, no delete_repo scope. For a merged PR <N>:

oid=$(gh pr view <N> --json mergeCommit --jq .mergeCommit.oid)
n=$(gh pr view <N> --json commits --jq '.commits|length')
base=$(gh pr view <N> --json baseRefOid --jq .baseRefOid)
git fetch origin pull/<N>/head:refs/tmp/pr<N>
git diff --name-only $base...refs/tmp/pr<N> | grep -v '^\.nexus/queue/' | sort > /tmp/auth.txt
# compare against each candidate the code tries (range.ts:109)
git diff --name-only ${oid}^1...${oid}    | grep -v '^\.nexus/queue/' | sort | diff - /tmp/auth.txt
git diff --name-only ${oid}~${n}...${oid} | grep -v '^\.nexus/queue/' | sort | diff - /tmp/auth.txt
git update-ref -d refs/tmp/pr<N>

Append the outcome to docs/features/pr-driven-delivery/live-acceptance-record.md. The
nxs-pr-acceptance harness stays available if a divergence ever shows up and needs a controlled
reproduction — it is no longer the required path.

Notes

This is the story the epic exists for: libs/pr-worktree/src/range.ts documents an ambiguity it
resolves by verification (squash vs. rebase are indistinguishable by topology alone), and only a
real merge can confirm the parent counts, SHAs, and post-branch-delete reachability those branches
key on.

The 2026-07-25 run found that every merge in this repo's history is a rebase merge, so the
ambiguous branch at range.ts:87 is the normal path here, not an edge case — and that
mergeCommit^1 is wrong for every multi-commit PR in the repo. The refuse-rather-than-guess design
is load-bearing. Closing the remaining strategies is three merge-button choices on PRs that were
going to be merged anyway.

Fixing anything this surfaces stays out of scope; divergences are filed and sized separately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    pipelineOrchestration of the Nexus stages and human-decision gatesstoryUser story (created by nxs-gh-create-story)testingVitest unit/component tests and Playwright e2e

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions