From 254fce5d579f75e5fe73816e6727118c6da0e330 Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 3 Sep 2026 10:29:30 +0000 Subject: [PATCH 1/2] [AISOS-2495] Clarify merge reconciliation documentation Detailed description: - Added a note to 'docs/guide/task-workflow.md' and 'docs/guide/feature-workflow.md' explaining that a merged pull request is terminally reconciled by its repository namespace and pull request number, rather than its head SHA. - Added a new unit test 'test_reconciliation_is_independent_of_head_sha' to 'tests/unit/workflow/test_pr_state.py' to verify and enforce that pull request matching and reconciliation are completely decoupled from its head SHA. Closes: AISOS-2495 --- docs/guide/feature-workflow.md | 3 ++ docs/guide/task-workflow.md | 3 ++ tests/unit/workflow/test_pr_state.py | 48 ++++++++++++++++++++++------ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/docs/guide/feature-workflow.md b/docs/guide/feature-workflow.md index a50b587c4..de0e19f91 100644 --- a/docs/guide/feature-workflow.md +++ b/docs/guide/feature-workflow.md @@ -188,6 +188,9 @@ The PR is now ready for human review. Merge when satisfied, or request changes t Once the PR is merged, Forge automatically completes the workflow by transitioning the Feature, all associated Tasks and Epics, and its parent Epic (if present) to **`Closed`** status in Jira. +!!! note "PR Merge Reconciliation" + A merged pull request is terminally reconciled by its repository namespace and pull request number, not by its head SHA. This design ensures that merge detection remains robust and unaffected by commit rebuilds, squashing, or rebase operations during review. + ## Q&A Mode At any approval gate, you can ask questions without triggering regeneration: diff --git a/docs/guide/task-workflow.md b/docs/guide/task-workflow.md index 5f5467c2f..4e6c9423c 100644 --- a/docs/guide/task-workflow.md +++ b/docs/guide/task-workflow.md @@ -153,6 +153,9 @@ If the agent classifies any review comments as *contested* (e.g., if they contra When the PR is merged, Forge marks the Task workflow complete. +!!! note "PR Merge Reconciliation" + A merged pull request is terminally reconciled by its repository namespace and pull request number, not by its head SHA. This design ensures that merge detection remains robust and unaffected by commit rebuilds, squashing, or rebase operations during review. + ## Comment Syntax At the triage and plan gates, Forge classifies comments by prefix: diff --git a/tests/unit/workflow/test_pr_state.py b/tests/unit/workflow/test_pr_state.py index 0aab124ef..a98aa1b37 100644 --- a/tests/unit/workflow/test_pr_state.py +++ b/tests/unit/workflow/test_pr_state.py @@ -265,7 +265,9 @@ def test_save_with_unknown_number_keys_by_url() -> None: ) assert "acme/docs:https://github.com/acme/docs/pull/30" in saved["pull_requests"] - assert saved["pull_requests"]["acme/docs:https://github.com/acme/docs/pull/30"]["number"] is None + assert ( + saved["pull_requests"]["acme/docs:https://github.com/acme/docs/pull/30"]["number"] is None + ) def test_save_without_number_or_url_is_noop() -> None: @@ -359,18 +361,14 @@ def _legacy_state() -> dict: def test_event_targets_pull_request_matches_legacy_bare_repo_key() -> None: state = _legacy_state() - event = _event( - repo="acme/legacy", native_id=99, url="https://github.com/acme/legacy/pull/99" - ) + event = _event(repo="acme/legacy", native_id=99, url="https://github.com/acme/legacy/pull/99") assert event_targets_pull_request(state, event) def test_activate_pull_request_for_event_hydrates_from_legacy_bare_repo_key() -> None: state = _legacy_state() - event = _event( - repo="acme/legacy", native_id=99, url="https://github.com/acme/legacy/pull/99" - ) + event = _event(repo="acme/legacy", native_id=99, url="https://github.com/acme/legacy/pull/99") activated = activate_pull_request_for_event(state, event) @@ -381,9 +379,7 @@ def test_activate_pull_request_for_event_hydrates_from_legacy_bare_repo_key() -> def test_save_migrates_legacy_bare_repo_key_to_numbered_key() -> None: state = _legacy_state() - event = _event( - repo="acme/legacy", native_id=99, url="https://github.com/acme/legacy/pull/99" - ) + event = _event(repo="acme/legacy", native_id=99, url="https://github.com/acme/legacy/pull/99") activated = activate_pull_request_for_event(state, event) activated["ci_status"] = "passed" @@ -392,3 +388,35 @@ def test_save_migrates_legacy_bare_repo_key_to_numbered_key() -> None: assert "acme/legacy" not in saved["pull_requests"] assert saved["pull_requests"]["acme/legacy:99"]["ci_status"] == "passed" assert saved["pull_requests"]["acme/legacy:99"]["lifecycle_node"] == "ci_evaluator" + + +def test_reconciliation_is_independent_of_head_sha() -> None: + """A pull request is terminally reconciled by its repository and PR number, not by its head SHA.""" + # Create two events with different head_sha but the same repo and native_id + event_1 = _event( + repo="acme/payments", native_id=42, url="https://github.com/acme/payments/pull/42" + ) + # Set different head SHAs on the ChangeRequests + event_1.change_request.head_sha = "sha1" + + event_2 = _event( + repo="acme/payments", native_id=42, url="https://github.com/acme/payments/pull/42" + ) + event_2.change_request.head_sha = "sha2" + + # State with key based on repo and number + key = "acme/payments:42" + state = { + "pull_requests": {key: {"number": 42, "url": event_1.change_request.url, "merged": False}} + } + + # Both events target the exact same pull request record regardless of the head SHA + assert event_targets_pull_request(state, event_1) is True + assert event_targets_pull_request(state, event_2) is True + + # Activating either event works and targets the same record + activated_1 = activate_pull_request_for_event(state, event_1) + activated_2 = activate_pull_request_for_event(state, event_2) + + assert activated_1["current_pr_number"] == 42 + assert activated_2["current_pr_number"] == 42 From fbf4d2212e12495db0c43e0a6c252b84a263f9a8 Mon Sep 17 00:00:00 2001 From: Forge Date: Thu, 3 Sep 2026 10:45:23 +0000 Subject: [PATCH 2/2] [AISOS-2495-review-fix] [AISOS-2495] review: address PR feedback Detailed description: - Added PR Merge Reconciliation documentation block to docs/guide/bug-workflow.md - Aligned Bug Workflow documentation with Task and Feature Workflow documentation Closes: AISOS-2495-review-fix --- docs/guide/bug-workflow.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guide/bug-workflow.md b/docs/guide/bug-workflow.md index 253890eb2..391bf50ab 100644 --- a/docs/guide/bug-workflow.md +++ b/docs/guide/bug-workflow.md @@ -110,6 +110,9 @@ After plan approval, Forge: 8. **Human review** gate. 9. **Post-merge summary:** After merge, Forge posts a fix summary and release note to the Jira ticket. +!!! note "PR Merge Reconciliation" + A merged pull request is terminally reconciled by its repository namespace and pull request number, not by its head SHA. This design ensures that merge detection remains robust and unaffected by commit rebuilds, squashing, or rebase operations during review. + --- ## Comment Syntax