Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### Change-request autofix fails closed on unknown merge state

- The shared change-request gate now requires `mergeStateStatus` to be exactly `CLEAN` or `HAS_HOOKS`. Missing, empty, and unknown REST fallback values cannot authorize autofix or RCA dispatch. Proposed in ContextualWisdomLab/.github#1492.

### Intel macOS native archives are bound to x86_64 bytes

- The release prescreener now requires every native member in an Intel macOS
Expand Down
33 changes: 33 additions & 0 deletions docs/doctoring/unknown-merge-state-fail-closed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Unknown merge state does not authorize change-request automation

검토 기준일: **2026-09-07**

## Problem

GitHub can temporarily report an unresolved mergeability state after a push.
The REST fallback normalizes that condition to an empty value. Treating the
empty value as clean allows automatic change-request handling without positive
mergeability evidence.

## Decision

The shared gate accepts only `CLEAN` and `HAS_HOOKS`. Missing, empty, or
unknown values return no clean review body, so neither autofix nor RCA dispatch
is authorized. Known dirty states retain the same behavior.

## Verification contract

`test_change_request_gates_fail_closed_on_unknown_merge_state` covers empty,
unknown, and absent values across the normalized body, autofix, and RCA entry
points. Hosted exact-head checks remain mandatory.

## Status

**Proposed** in ContextualWisdomLab/.github#1492. Protected `main` remains the
release authority.

## Reference

GitHub. (n.d.). *REST API endpoints for pull requests*. GitHub Docs. Retrieved
September 7, 2026, from
https://docs.github.com/en/rest/pulls/pulls
6 changes: 6 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

이 문서는 제품·기술·운영 Gap을 현재 문서와 현재 GitHub 상태에 묶어 두는 기준선이다. 새 작업은 먼저 이 문서의 Gap ID를 PR 설명과 테스트 증거에 연결하고, PR의 정확한 exact HEAD·Checks·리뷰를 다시 수집한 뒤 구현한다. 표의 상태는 작성 시점의 관측값이므로, 병합 판단에는 재사용하지 않는다. 이 인벤토리는 스냅샷이며 merge authorization이 아니다.

### 2026-09-07 unknown merge-state fail-closed amendment

- **Gap:** an empty or absent REST fallback `mergeStateStatus` can pass the change-request gate as if mergeability were proven.
- **Action:** ContextualWisdomLab/.github#1492 requires `CLEAN` or `HAS_HOOKS` explicitly before autofix or RCA classification.
- **Status:** Proposed; exact-head hosted Checks, independent review, ordinary protected integration, and post-merge current-main verification remain required.

### 2026-09-19 exact-head incident delta

| Gap ID | 상태 | exact-head evidence | causal owner / next gate |
Expand Down
2 changes: 1 addition & 1 deletion scripts/ci/pr_review_fix_scheduler.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def latest_current_head_opencode_review(pr: dict[str, Any]) -> dict[str, Any] |
def _clean_change_request_body(pr: dict[str, Any]) -> str | None:
"""Return normalized exact-head OpenCode review text for a clean PR."""
merge_state = str(pr.get("mergeStateStatus") or "").upper()
if merge_state and merge_state not in {"CLEAN", "HAS_HOOKS"}:
if merge_state not in {"CLEAN", "HAS_HOOKS"}:
return None
review = latest_current_head_opencode_review(pr)
if review is None:
Expand Down
31 changes: 31 additions & 0 deletions tests/test_pr_review_fix_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,37 @@ def test_change_request_requires_current_head_opencode_review():
assert not fix.change_request_is_autofixable(stale_review_pr)


def test_change_request_gates_fail_closed_on_unknown_merge_state():
"""Unknown or missing merge state cannot authorize automatic repair."""
head = "a" * 40
body = "Actionable source-backed finding with suggested diff."
current_review = {
"state": "CHANGES_REQUESTED",
"author": {"login": "opencode-agent"},
"commit": {"oid": head},
"body": body,
}

for merge_state in ("", "UNKNOWN"):
pull_request = make_pr(
headRefOid=head,
mergeStateStatus=merge_state,
reviews={"nodes": [current_review]},
)
assert fix._clean_change_request_body(pull_request) is None
assert not fix.change_request_is_autofixable(pull_request)
assert not fix.change_request_requires_rca(pull_request)

pull_request = make_pr(
headRefOid=head,
reviews={"nodes": [current_review]},
)
del pull_request["mergeStateStatus"]
assert fix._clean_change_request_body(pull_request) is None
assert not fix.change_request_is_autofixable(pull_request)
assert not fix.change_request_requires_rca(pull_request)


def test_process_queue_dispatches_same_repo_current_head(monkeypatch, capsys):
"""The queue path dispatches one same-repository autofix."""
pr = make_pr()
Expand Down
Loading