|
| 1 | +# 2026-09-18 — 충돌 해소가 «한쪽 부모의 정의»를 떨어뜨렸는지 센다 (rustjava-count-symbols-dropped-from-second-parent-on-resolution) |
| 2 | + |
| 3 | +채택 제안 `2026-09-17-union-restores-silently-dropped-makeconcat#p0`. ★**제품 코드 0줄** — 검사기 1개 + 의도 규약 + 기존 워크플로에 잡 하나. |
| 4 | + |
| 5 | +## ⓑ 먼저 — 이미 덮는 축이 «없다»는 것을 실행으로 확인했다 |
| 6 | + |
| 7 | +★**이 게이트가 이 티켓을 끝낼 수 있었으므로 먼저 쟀다.** `origin/main` 에 손실을 **재구성**하고 기존 축을 전부 돌렸다: |
| 8 | + |
| 9 | +| 축 | 손실 트리에서 | |
| 10 | +|---|---| |
| 11 | +| 생성기 실행 | **rc=0** | |
| 12 | +| `audit-fixture-single-defect.py` | **rc=0**(검사 18 · 무결함 5 · 합계 23) | |
| 13 | +| `cargo test --test test_class_format` | **21 passed** | |
| 14 | +| `cargo test --test test_fixture_pins` | **3 passed** | |
| 15 | +| `check-dod-ci-parity` | **rc=0** | |
| 16 | +| `git status -- test-data/indy` | **0줄** | |
| 17 | +| 충돌 마커 | **0** | |
| 18 | + |
| 19 | +⇒ ★**아무것도 잡지 못한다.** 게이트는 발화하지 않는다. |
| 20 | + |
| 21 | +★★**왜 조용한지를 «정확히» 알아냈다 — 이것이 설계를 정했다**: 손실은 **떨어진 이름들이 서로 자기완결일 때만** 조용하다. |
| 22 | +자기완결이 아닌 슬라이스를 떨어뜨리면 `AttributeError` 로 **시끄럽게 죽는다**(그것도 쟀다). |
| 23 | +그리고 ★**충돌의 한쪽을 통째로 택하는 것이 바로 «자기완결 슬라이스를 떨어뜨리는 것»**이다. |
| 24 | + |
| 25 | +## ⓐ 「3건」을 다시 셌다 — ★**4건이다** |
| 26 | + |
| 27 | +두 해소 커밋 각각에서 base·ours·theirs 의 정의 집합을 세고 `theirs − 결과` 를 냈다: |
| 28 | + |
| 29 | +``` |
| 30 | +e53b2142 (merge-base 4b89f18a) base 14 · ours 20 · theirs 18 · 결과 20 → 떨어진 것 4 |
| 31 | +514d5b08 (merge-base 42e99a7e) base 18 · ours 20 · theirs 18 · 결과 20 → 떨어진 것 4 |
| 32 | + const LINKED · const MAKECONCAT_DESCRIPTOR · def make_concat_call_site · method fieldref |
| 33 | +``` |
| 34 | +제안은 **three** 라 적었다. ★**내 정의로는 4**이고, 넷째는 `MAKECONCAT_DESCRIPTOR` 다. ※쓰기 루프는 `if __name__` 안이라 «정의»가 아니어서 안 센다 — 그 한계는 아래에 적는다. |
| 35 | + |
| 36 | +## ⓒ 「최상위 정의」를 무엇으로 볼 것인가 — ★실제 4건에서 거꾸로 정했다 |
| 37 | + |
| 38 | +| 떨어진 것 | 계급 | |
| 39 | +|---|---| |
| 40 | +| `LINKED` · `MAKECONCAT_DESCRIPTOR` | 모듈 수준 대문자 대입 | |
| 41 | +| `make_concat_call_site` | 모듈 수준 `def` | |
| 42 | +| ★`fieldref` | ★**`class Pool` «안»의 메서드**(들여쓰기 4) | |
| 43 | + |
| 44 | +⇒ ★★**「최상위」만 보면 4건 중 3건만 잡는다.** 그래서 **한 단계 중첩(메서드)까지** 본다. Rust 도 같은 모양으로 넣었다(`fn`·`type`·`const`·`impl` 안 메서드). |
| 45 | + |
| 46 | +## ⓓ 스쿼시와의 상호작용 |
| 47 | + |
| 48 | +`rustjava` 는 `contracts/upstream-sync-repos.conf` **등재**라 **`--merge` 로 착지**한다 ⇒ main 에 **부모2가 있다**(최근 200 커밋 중 머지 **83**). |
| 49 | +★**그러나 이 검사기가 겨냥하는 것은 착지가 아니라 «브랜치 내부 base 당김 머지»**이고(사고 둘 다 그것이었다 · 최근 83 중 **26건**), |
| 50 | +그것은 **착지 방식과 무관하게** 존재한다. ⇒ **PR 시점에** 검사하므로 스쿼시 repo 에서도 같은 값을 한다. |
| 51 | +실측: 부모 1개짜리 범위 → `0 merge(s)` · **rc=0**(조용히 통과하고, 그것이 문서화된 동작이다). |
| 52 | + |
| 53 | +## 의도와 사고를 가르는 목록 — ★**커밋 trailer 다** |
| 54 | + |
| 55 | +``` |
| 56 | +Dropped-from-theirs: Pool.fieldref -- superseded by the new pool builder, see <round> |
| 57 | +Dropped-from-theirs: * -- recorded as a merged ancestor only, our tree is authoritative |
| 58 | +``` |
| 59 | + |
| 60 | +- **어디에**: 그 **머지 커밋 메시지**에. |
| 61 | +- **누가 언제 지우는가**: ★**지울 필요가 없다** — trailer 는 **자기 커밋 하나만** 면제한다. |
| 62 | + ★티켓이 경고한 「영구 목록은 시간이 지나면 검사기를 무력화한다」가 **구조적으로 불가능**하다(파일이면 항목이 쌓여 검사기를 끈다). |
| 63 | +- **왜**: `--` 뒤 사유가 **형식상 필수**다(없으면 매치되지 않아 면제되지 않는다). |
| 64 | + |
| 65 | +## ★★설계를 한 번 갈아엎었다 — 개악 시험이 «내 검사기»의 구멍을 잡았다 |
| 66 | + |
| 67 | +초판에는 **구조적 면제**가 있었다: 「결과 트리 == 부모1 트리면 `-s ours` 이니 건너뛴다」. |
| 68 | +★**개악 시험이 그것을 무너뜨렸다** — theirs 를 통째로 버린 합성 머지도 **트리가 부모1과 같아서** |
| 69 | +★**면제에 삼켜져 rc=0 이 됐다.** 그런데 그게 **이 검사기가 존재하는 이유인 바로 그 실패**다. |
| 70 | +⇒ ★**구조적 면제를 걷어내고 «선언»(`*` 와일드카드)으로 바꿨다.** `-s ours` 도 이제 **말해야** 한다. |
| 71 | +★이 교체가 이 회차에서 가장 값한 측정이다 — 양방향을 안 돌렸으면 **끈 채로 착지**했다. |
| 72 | + |
| 73 | +## 합격선 — 양방향 전건 |
| 74 | + |
| 75 | +| 축 | 결과 | |
| 76 | +|---|---| |
| 77 | +| ⒜ theirs 정의를 버린 해소 | ★**rc=1** · `def kept_by_theirs — in the merged-in branch, not in the result` (**이름을 말한다**) | |
| 78 | +| ⒝ 아무것도 안 떨어뜨린 합집합 해소 | **rc=0** | |
| 79 | +| ⒞ trailer 등재 | **rc=0** · ★**지우면 다시 rc=1** · `*` 와일드카드도 **rc=0** | |
| 80 | +| ⓓ 스쿼시(부모 1개) | `0 merge(s)` · **rc=0** | |
| 81 | +| 실사고 재현 | ★**손으로 찾은 것과 같은 분할을 기계가 낸다** — 생성기 **4건(사고)** + `.rs` **2건(의도)** | |
| 82 | + |
| 83 | +## ⒟ 오탐 — ★**세었다**(「적을 것이다」로 적지 않는다) |
| 84 | + |
| 85 | +최근 **200커밋 · 머지 83건**에 소급: **8건 red · 정의 19개**(머지의 **9.6%**) · **4.4초/머지**(83건 368초). |
| 86 | +★**초판과 최종판이 같은 수를 낸다** — 구조적 면제를 걷어내 `-s ours` 1건이 다시 잡히지만, 그 1건은 애초에 면제 대상이었다. |
| 87 | +★**그리고 CI 는 이 범위를 돌지 않는다** — `origin/main..HEAD`(= PR 범위)만 본다. 과거 머지는 재검사되지 않으므로 이 8건이 main 을 red 로 만들지 않는다. |
| 88 | +⇒ PR 하나에 내부 머지는 보통 **0~2건**이라 **실비용 ≈ 0~9초**. |
| 89 | + |
| 90 | +★**그 8건의 성격을 확인했다**(추측하지 않았다): 2건은 **알려진 사고** · 1건은 `-s ours` 조상 기록 · |
| 91 | +나머지는 **upstream 동기(S5~S8)** 인데, ★`decode_str`·`encode_str` 은 **결과 트리 어디에도 없다**(이동이 아니라 실제 소실 — 직접 확인). |
| 92 | +⇒ ★**그것이 «발견»인지 «수용된 판단»인지는 그 리니지가 답할 몫**이고 이 회차가 정하지 않는다. |
| 93 | + |
| 94 | +## ★잃는 것 — 「없다」로 적지 않는다 |
| 95 | + |
| 96 | +- ★**이름은 남고 «본문이 비어진» 경우는 못 잡는다.** 집합 비교라 「있긴 한데 아무것도 안 한다」는 통과한다. |
| 97 | +- ★**리네임·파일 이동을 소실과 구별하지 못한다** — 옛 경로에서는 «떨어진 것»으로 보인다. |
| 98 | +- ★**trailer 는 우회로다.** 급하면 누구나 적고 지나간다. 브레이크는 「사유가 형식상 필수」와 「커밋에 남아 보인다」뿐이다. |
| 99 | + ★**더 조이지 않았다** — 실적 없는 검사기에 과설계라고 판단했다(그 판단 자체가 대가다). |
| 100 | +- ★**머지마다 4.4초가 붙는다** · DoD 블록에도 들어가 **로컬 실행에도** 붙는다. |
| 101 | + |
| 102 | +## CI 배선 |
| 103 | + |
| 104 | +새 워크플로 파일 **0** — `rust.yml` 에 `merge_drops` 잡을 얹었다(`fetch-depth: 0` — 얕은 체크아웃에는 머지 부모가 없다). |
| 105 | +★**파리티 락이 같이 움직인다**: `python3` 스텝은 DoD 블록에도 있어야 하므로 `CLAUDE.md` DoD 블록에 같은 줄을 넣었고, |
| 106 | +`check-dod-ci-parity` 가 **「명령 7개 · toolchain 2개로 둘 다 일치」**로 답한다. |
0 commit comments