diff --git a/REPORT.md b/REPORT.md index c7b4ede5..cce6198b 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,5 +1,40 @@ # REPORT +## [2026-08-27] upstream 동기 근인 확정 — 게이트③ `--squash` 가 계보를 버린다 (rustjava-upstream-sync-squash-defeats-convergence) +- 무엇을: S1~S4(PR #11·#13·#16·#17)가 **전건 `merged=true`** 인데도 `merge-base origin/main upstream/main` + 이 fork 시점 **`62cf0c6a`** 그대로이고 behind **33** 이 줄지 않던 근인을 확정했다. 근인은 게이트③ + 제품 repo **`--squash`** 다 — 네 머지커밋의 **부모가 전건 1개**이고(커밋 7·10·15·21이 각각 1로 접힘), + 반면 브랜치 `34a4235` 는 부모 **2개**(`c80638a`+`3296139`)인 진짜 머지였다. + ⇒ ★**계보는 브랜치에 있었고 게이트③이 버렸다** — cherry-pick·브랜치 재작성 가설은 실측으로 기각. + 처방(갈래 ⒞의 ⒜)으로 `origin/main` 위에 `git merge -s ours 3296139` 를 얹었다. `1f356ae`·`af4f6f8`· + `822504b` 는 전부 `3296139` 의 조상이라 **한 머지가 네 컷을 덮는다**. +- 왜: 내용은 이미 들어와 있는데 **계보가 없어서** git 이 영원히 「33 뒤」라고 답했고, 그 결과 + **다음 회차가 앞 회차가 이미 닫은 충돌을 처음부터 다시 열었다.** + ★**근거는 «충돌 총수»가 아니라 «재생분»이다** — 총수는 회차마다 기준(복원 전/후)이 달라 비교가 안 된다: + ★**S2 15 중 10 재생 → S4 20 중 18 재생**(둘 다 계보가 끊긴 회차) · ★**S3 는 계보가 온전해 재생 0**. + 기준을 단 회차별 표: **S1 2**(복원 불요) · **S2 15 → 5** · **S3 11**(★계보 온전 · 복원 불요) · **S4 20 → 2**. + ★★**S3 는 이 병의 «반례»다** — `merge-base` 가 `af4f6f8` 로 정상이었고 복원을 하지 않았다. + 계획서 §5 는 S4 를 「새 충돌 **0**」으로 예측했는데 실측은 **복원 전 20 · 복원 후 2** 로 + **어느 쪽으로 읽어도 빗나갔다**. 그 표의 수는 + **base 가 전진한다는 전제 위의 수**였고 그 전제가 깨져 있었다. +- 사용자 영향: **제품 코드 변경 0** — 트리 오브젝트 SHA 가 `origin/main` 과 **동일**(`c4f57d10…`)하므로 + 런타임 동작은 1바이트도 바뀌지 않는다. 얻는 것은 **다음 동기 회차의 비용**이다: + `merge-base` **`62cf0c6a` → `3296139c`** · behind **33 → 18** · S1~S4 컷 조상 **0/4 → 4/4**. +- 검증: 트리 변경 0 근거 = **트리 SHA 동일**(`git diff 3a59776..c118d21` **0줄**). + ★`git diff --stat` 빈 출력은 `-s ours` 에서 **정의상 항상 참**이라 근거로 쓰지 않았다(S4 워크로그 교훈). + `.rs` 변경 0 이므로 cargo 4종은 트리 불변으로 담보된다. `scripts/check-worklog-json.py` rc=0. +- ★**후속 추천 (1) — 총괄 몫. 이 세션은 손대지 않았다**: + 게이트③ **제품 repo `--squash` 규율에 «upstream 동기 PR 예외»를 넣을지 총괄이 판정하라** + (`~/orchestrator/ORCHESTRATOR.md` 2곳 · `~/orchestrator/templates/merge-ticket.tpl` 스니펫 1곳 — + ★**orchestrator 소관이라 RustJava 세션이 고치지 않는다**). + ★★**이 PR 자체가 `--squash` 로 착지하면 위 처방이 그대로 무효가 된다** — 부모 2개가 1개로 접히며 + `3296139` 조상 관계가 사라지고 `merge-base` 는 `62cf0c6a` 로 되돌아간다. 반드시 `gh pr merge --merge`. + ★**회차마다 브랜치에 `-s ours` 를 다시 넣는 현행 방식은 러닝머신이다** — S4 의 `c80638a` 가 정확히 그 + 처방이었는데 PR #17 의 스쿼시에 함께 사라졌고, 이 리니지에서 **네 번** 반복됐다. + ★★**`wie` 도 같은 함정 위에 있다 — behind 1067.** 예외 판정은 RustJava 단독 문제가 아니다. +- ★**후속 추천 (2)**: S5~S7 의 「새 충돌」을 **이 PR 이 `--merge` 로 착지한 «뒤»** 재측정하라 — + `merge-base` 가 `3296139` 인 상태에서 잰 수라야 다음 회차의 실제 기준선이다(워크로그 `#p1`). + ## [2026-08-27] upstream 동기 S4 — 컷 `3296139` 머지 (rustjava-upstream-sync-s4) - 무엇을: upstream `3296139`(#184 CLI classpath) 까지 **8커밋**을 머지했다(GlobalRef · CDC text API · monitor 인자 일반화 · classfile 오류 은닉 · tokio 1.53). 충돌 **2** 해소 — diff --git a/STATE.md b/STATE.md index c6bdad74..4ef328a5 100644 --- a/STATE.md +++ b/STATE.md @@ -1,6 +1,17 @@ # STATE ## 진행중 +- [rustjava-upstream-sync-squash-defeats-convergence] ★**S1~S4 가 착지하고도 fork 가 upstream 에 + 한 걸음도 가까워지지 않은 근인을 확정하고 계보를 기록했다.** 근인 = 게이트③ 제품 repo **`--squash`**. + 증명은 **머지커밋 부모 수**다 — `6bfe97c4`·`11ef5010`·`4bb796de`·`3a597768` **전건 1개**(커밋 7·10·15·21이 + 각각 1로 접힘). ★**반증 시도는 실패했다(= 가설이 맞다)**: 브랜치 `34a4235` 는 부모 **2개** + (`c80638a`+`3296139`)인 진짜 머지 ⇒ **계보는 브랜치에 있었고 스쿼시가 버렸다**(cherry-pick 가설 기각). + 처방 = `origin/main` 위 `git merge -s ours 3296139` — **트리 오브젝트 SHA 동일**(`c4f57d10…`)로 트리 변경 0. + ★`git diff --stat` 빈 출력은 `-s ours` 정의상 항상 참이라 근거로 쓰지 않았다(S4 교훈). + 효과: `merge-base` **`62cf0c6a` → `3296139c`** · behind **33 → 18** · 컷 조상 **0/4 → 4/4**. + ★★**이 PR 이 `--squash` 로 착지하면 위 전부가 무효다** — 반드시 `gh pr merge --merge`. + ★**회차마다 `-s ours` 를 다시 넣는 지금 방식은 러닝머신이다** — S4 의 `c80638a` 가 정확히 그 처방이었는데 + PR #17 의 스쿼시에 함께 지워졌다(이 리니지에서 네 번 반복). **PR 대기 — 게이트③ 미착지.** - [rustjava-upstream-sync-s4] upstream 컷 `3296139`(#184 GlobalRef · CLI classpath · CDC text) 머지 — 충돌 **2** 해소(`java/lang/thread.rs` · `jvm/src/jvm.rs`). ★**첫 조치가 `git merge -s ours --no-ff 822504b`** — 그것이 ★**충돌 20 → 2**를 만들었다. **PR 대기 — 게이트③ 미착지.** diff --git a/docs/upstream-sync-approach.md b/docs/upstream-sync-approach.md index eefaf9f7..1d5c59bc 100644 --- a/docs/upstream-sync-approach.md +++ b/docs/upstream-sync-approach.md @@ -221,6 +221,45 @@ upstream 은 여기서만 `UTF-8`/`EUC-KR` 하드코딩을 유지한다 ⇒ 그 ★**「새 충돌」은 «해당 컷에서 처음 충돌하는 파일 수»다.** 앞 회차가 착지하면 뒤 회차의 기준선이 바뀌므로 **각 회차 착수 시 재측정이 필수**다(§0 의 교훈 그대로). +> ★★**[2026-08-27 정정 — 이 표의 「새 충돌」은 «base 가 전진할 때»의 수다. 그 전제는 S1~S4 에서 깨져 있었다.]** +> 위 「앞 회차가 착지하면 뒤 회차의 기준선이 바뀐다」는 **거짓이었다.** 게이트③이 제품 repo 를 +> `--squash` 로 착지시키므로 PR 브랜치의 upstream 머지 부모가 버려지고, `origin/main` 은 **내용만** +> 받은 채 **계보는 fork 시점(`62cf0c6a`)에 머문다.** ⇒ `git merge-base origin/main upstream/main` 이 +> 전진하지 않으므로 **다음 회차는 앞 회차가 이미 닫은 충돌을 처음부터 다시 연다.** +> +> 실측(S1~S4 착지 «후»): 머지커밋 `6bfe97c4`·`11ef5010`·`4bb796de`·`3a597768` **전건 부모 1개**(=squash) · +> `merge-base` **`62cf0c6a` 불변** · behind **33**. +> +> ★★**회차별 충돌은 «기준»을 달지 않으면 비교할 수 없다**(초판이 그 실수를 했다 — 아래 표의 네 칸이 +> 서로 다른 기준에서 하나씩 뽑혀 **우상향 계열**로 읽혔다. 그 계열은 **어느 기준에서도 성립하지 않는다**): +> +> | 회차 | 그냥 재면(복원 전) | `-s ours` 복원 후 | 계보 상태 | ★재생분(앞 회차가 이미 닫은 자리) | +> |---|---|---|---|---| +> | S1 | **2** | — (**복원 불요** · fork 시점 base) | 해당 없음 | — | +> | S2 | **15** | **5** | 끊김 → 복원함 | ★**15 중 10** | +> | S3 | **11** | — (**복원 불요**) | ★**온전**(`merge-base` = `af4f6f8`) | ★**0** | +> | S4 | **20** | **2** | 끊김 → 복원함 | ★**20 중 18** | +> +> ★★**S3 는 «사례»가 아니라 «반례»다 — 계열에서 빼기만 하지 말고 이 문장을 남겨라.** +> S3 는 S2 브랜치 «위에» 쌓아 `merge-base` 가 `af4f6f8` 로 **정상**이었고 `-s ours` 복원을 +> **하지 않았다**(`s3.done.md` §0-⑴). 그 **11** 은 **계보가 온전한 상태의 순수 신규 충돌**이고 +> **재생분 0** 이다. ⇒ 「계보 미수렴의 비용」 계열에 넣으면 **근거가 아니라 반증**이 된다. +> +> ★**그래서 이 병의 근거는 «충돌 총수»가 아니라 «재생분»이다** — 기준이 하나이고 S3 도 자연히 설명된다: +> ★**S2 10 → S4 18**(둘 다 계보가 끊긴 회차 · 출처 `s2.done.md` 열거 · `s4.done.md` §2) · **S3 0**(계보 온전). +> +> ★**예측 대 실측 — «복원 전»(계획서가 쓴 `merge-tree origin/main <컷>` 과 같은 기준)으로 통일**: +> S1 예측 2 ↔ 실측 **2** · S2 +5 ↔ **15** · S3 +9 ↔ **11** · S4 **0 ↔ 20**. +> ⇒ S4 는 ★**복원 전 20 · 복원 후 2** 로 **어느 쪽으로 읽어도 「0」 예측을 빗나갔다**. +> +> ⇒ ★**S5~S7 의 「0」(`Cargo.lock`만)도 같은 전제 위의 수다. 그대로 믿지 마라 — «하한»으로 읽어라.** +> 이 표를 쓰기 전에 **반드시** `git merge-base origin/main upstream/main` 을 먼저 재고, 그것이 +> 직전 컷으로 전진해 있지 않으면 **해당 회차 착수 시 `merge-tree` 로 전량 재측정**한다. +> +> **처분**: 계보 기록 커밋(`git merge -s ours <컷>` · 트리 변경 0)으로 `merge-base` 를 `3296139` +> 까지 끌어올렸다(behind 33 → 18). ★**단 이 커밋 자체가 `--squash` 로 착지하면 무의미하다** — +> 게이트③ 예외 판정은 총괄 소관(`REPORT.md` 후속 추천 참조). + ★**S1~S3 이 이 동기화의 «전부»다** — 세 회차가 판단을 다 쓰고, 각각 **한 축씩만** 다룬다. 검수자가 한 회차에서 읽어야 하는 것은 **우리 해소분**이지 upstream 원본 diff 가 아니다: diff --git a/docs/worklog/2026-08-27-upstream-sync-squash-convergence.json b/docs/worklog/2026-08-27-upstream-sync-squash-convergence.json new file mode 100644 index 00000000..6b228768 --- /dev/null +++ b/docs/worklog/2026-08-27-upstream-sync-squash-convergence.json @@ -0,0 +1,54 @@ +{ + "schema": 1, + "date": "2026-08-27", + "title": "S1~S4 가 착지하고도 fork 가 upstream 에 가까워지지 않은 근인 확정(게이트③ --squash) + 계보 기록으로 behind 33 → 18", + "services": [ + "RustJava" + ], + "taskId": "rustjava-upstream-sync-squash-defeats-convergence", + "summary": "S1~S4(PR #11·#13·#16·#17)가 전건 merged=true 인데 merge-base origin/main upstream/main 은 fork 시점 62cf0c6a 그대로였고 behind 33 이 줄지 않았다. 근인은 게이트③ 제품 repo --squash 다 — 네 머지커밋(6bfe97c4·11ef5010·4bb796de·3a597768) 전건 부모 1개로, 7·10·15·21 커밋이 각각 1커밋으로 접혔다. 가설 반증 시도는 실패했다(= 가설이 맞다): 브랜치 feat/rustjava-upstream-sync-s4 의 34a4235 는 부모 2개(c80638a + 3296139)인 진짜 머지이므로 계보는 브랜치에 있었고 스쿼시가 버린 것이다. 갈래 ⒞ 채택 — ⒜ origin/main 위에서 git merge -s ours 3296139(트리 SHA 동일 c4f57d10 = 트리 변경 0)로 네 컷 전건을 조상으로 기록해 merge-base 62cf0c6a → 3296139c · behind 33 → 18 · 컷 조상 0/4 → 4/4. ⒝ 게이트③ 규율 예외는 총괄 소관이라 손대지 않고 REPORT.md 후속 추천으로 올렸다. ★이 PR 자체가 --squash 로 착지하면 ⒜는 무효다.", + "changes": [ + "계보 기록 — origin/main(3a59776) 위에서 git merge -s ours 3296139. 1f356ae·af4f6f8·822504b 는 전부 3296139 의 조상이라 한 머지가 네 컷을 덮는다. 트리 변경 0 근거는 트리 오브젝트 SHA 동일(c4f57d10…)이다 — git diff --stat 빈 출력은 -s ours 에서 정의상 항상 참이라 근거로 쓰지 않았다(S4 워크로그 교훈).", + "docs/upstream-sync-approach.md §5 — 「앞 회차가 착지하면 뒤 회차의 기준선이 바뀐다」가 거짓이었음을 정정. 표의 「새 충돌」이 base 전진 전제 위의 수임을 명시하고, 표를 쓰기 전 merge-base 를 먼저 재고 전진해 있지 않으면 merge-tree 로 전량 재측정하도록 규율화. 예측 대 실측 불일치(S3 +9↔11 · S4 0↔20) 기재.", + "docs/worklog/2026-08-27-upstream-sync-squash-convergence.{md,json} 신설.", + "STATE.md · REPORT.md 갱신." + ], + "deploy": { + "sha": "", + "urls": [] + }, + "verification": "재실측(회신 시점): merge-base origin/main upstream/main = 62cf0c6a(2026-06-28) · S1~S4 컷 origin/main 조상 0/4(upstream/main 조상 4/4) · rev-list --count origin/main..upstream/main = 33 · PR #11/#13/#16/#17 merged=true(커밋 7/10/15/21) · 회차별 충돌은 기준을 달아야 비교된다(초판의 2→5→11→20 우상향 계열은 네 수가 서로 다른 기준에서 뽑힌 것이라 성립하지 않는다) — S1 2(복원 불요) · S2 그냥 재면 15/복원 후 5 · S3 11(★계보 온전 merge-base=af4f6f8 · 복원 불요) · S4 복원 전 20/복원 후 2. ★계보 미수렴의 비용은 «재생분»으로 잰다(기준 하나): S2 15 중 10 → S4 20 중 18 · ★S3 는 계보가 온전해 재생 0 = 반례다. 근인 증명: gh api 로 얻은 네 merge_commit_sha 의 git cat-file -p parent 줄 수 전건 1. 반증 시도: 브랜치 34a4235 parent 2개(c80638a·3296139) + merge-base --is-ancestor 3296139 = YES ⇒ 계보는 브랜치에 있었고 스쿼시가 버렸다. 처방 후: tree BEFORE(3a59776)=tree AFTER(c118d21)=c4f57d10bce2087cebe2e1156f716f6ba8f75335 · git diff 3a59776..c118d21 = 0줄 · merge-base → 3296139c · behind 33 → 18 · 컷 조상 4/4. ★제품 코드(.rs) 변경 0 이라 cargo 축은 트리 불변으로 담보된다(트리 SHA 가 origin/main 과 동일).", + "issues": [ + "★[fix 회차 정정] 초판이 적은 「회차별 충돌 2 → 5 → 11 → 20」을 우상향 계열로 읽은 것은 폐기했다 — 네 수가 서로 다른 기준(S2 는 복원 후, S4 는 복원 전)에서 뽑혔고 기준을 통일하면 복원후 2·5·11·2 · 복원전 2·15·11·20 으로 어느 쪽도 우상향이 아니다. 또 S3(11)는 merge-base 가 af4f6f8 로 온전한 회차라 계열의 «사례»가 아니라 «반례»다. 근거를 «재생분»(S2 10 → S4 18 · S3 0)으로 교체했다. ★결론·인과(--squash 가 계보를 죽인다 · 착지는 --merge 여야 한다)는 검수자가 스크래치 클론에서 실증했고 불변이다.", + "★이 PR 이 --squash 로 착지하면 ⒜는 무효다 — 부모 2개가 1개로 접히며 3296139 조상 관계가 다시 사라지고 merge-base 는 62cf0c6a 로 되돌아간다. 반드시 gh pr merge --merge 로 착지시켜야 한다.", + "★같은 처방이 이미 한 번 지워졌다 — S4 브랜치의 c80638a(「record upstream cut 822504b as merged (tree unchanged)」)가 -s ours 계보 기록이었는데 PR #17 의 스쿼시에 함께 사라졌다. ⇒ 브랜치 처방만으로는 매 회차 무효화된다.", + "머지하지 않았다 — 게이트②·③은 별 세션이다.", + "upstream/main 헤드까지 -s ours 하지 않았다. S5~S7 내용은 실제로 없어 그것까지 얹으면 거짓 주장이 되고 남은 물량이 조용히 사라진다.", + "★wie 도 같은 함정 위에 있다(behind 1067) — 이 repo 에서 고칠 수 없다." + ], + "proposals": [ + { + "title": "게이트③ 제품 repo `--squash` 규율에 upstream 동기 PR 예외를 넣을지 총괄이 판정하라", + "plainSummary": "우리 fork 는 원본 저장소의 변경을 정기적으로 받아온다. 그런데 머지 방식이 «이력을 하나로 접는» 방식이라, 받아온 사실 자체가 기록되지 않는다. 그래서 다음 번에도 git 은 «아직 안 받았다»고 답하고 같은 충돌을 처음부터 다시 낸다.", + "userBenefit": "동기화 회차마다 앞 회차가 이미 푼 충돌을 다시 푸는 낭비가 사라진다. 실측 근거는 «재생분»이다(충돌 총수는 회차마다 복원 전/후 기준이 달라 비교 불가) — 계보가 끊긴 회차에서 S2 15 중 10 → S4 20 중 18 이 앞 회차가 이미 닫은 자리의 재생이었다. ★계보가 온전했던 S3 는 재생 0 이다. 예외가 없으면 남은 S5~S7 에서 더 나빠진다.", + "why": "게이트③ 규율이 「제품 repo = --squash」를 못박는데(ORCHESTRATOR.md 2곳 · templates/merge-ticket.tpl), --squash 는 PR 의 부모 관계를 버리므로 upstream 커밋이 조상으로 기록될 방법이 없다. 실측: 네 머지커밋 전건 부모 1개 · merge-base 는 fork 시점 62cf0c6a 불변 · behind 33 불변. 반면 브랜치 34a4235 는 부모 2개였다 ⇒ 계보를 버린 것은 게이트③이다. ★같은 템플릿이 형제 증상을 이미 알고 있다: 「부모 PR 이 --squash 로 착지하면 자식 PR 은 공통 조상이 사라져 add/add 로 전건 충돌한다」. ★wie 도 같은 함정 위에 있다(behind 1067) — 예외는 RustJava 단독 문제가 아니다.", + "tradeoff": "⑴upstream 동기 PR 만 --merge 예외로 열면 계보가 남고 다음 회차 base 가 전진하지만, 제품 repo 이력에 머지커밋이 섞여 「티켓별 1커밋」 관행이 그 회차만 깨진다(orchestrator-ops 가 이미 같은 이유로 반대 방향 예외를 쓰고 있으므로 선례는 있다). ⑵예외 없이 회차마다 -s ours 계보 커밋을 넣는 방식은 이번처럼 스쿼시에 함께 지워져 무효다 — 실제로 S4 의 c80638a 가 그렇게 사라졌다. ⑶현상 유지면 S5~S7 이 앞 회차 충돌을 전부 다시 열고, 계획서 §5 의 「충돌 0」 예측은 남은 셋에서도 무의미하다.", + "effort": "S — 규율 문안 2곳(ORCHESTRATOR.md)과 스니펫 1곳(templates/merge-ticket.tpl). 판정 자체는 XS.", + "target": "~/orchestrator/ORCHESTRATOR.md · ~/orchestrator/templates/merge-ticket.tpl (★orchestrator 소관 — RustJava 세션이 고치지 않는다)" + }, + { + "title": "S5~S7 의 「새 충돌」을 재측정할지 결정하라 — 표의 수는 전부 전진하는 base 위의 예측이다", + "plainSummary": "남은 세 회차의 난이도 예측이 «앞 회차가 제대로 반영됐다»는 가정 위에 쓰여 있었다. 그 가정이 틀렸던 것이 이번에 밝혀졌으니, 예측을 다시 재야 계획이 쓸모 있다.", + "userBenefit": "남은 회차의 크기를 실제 수로 잡을 수 있다 — 지금은 「충돌 0」으로 적힌 회차가 실제로 20충돌일 수 있고, 그러면 티켓 size/timeout 이 처음부터 틀린다(S4 가 정확히 그랬다).", + "why": "§5 표는 S4~S6 을 「충돌 0 · 물량」으로 잡았는데 S4 실측은 20 이었다. 예측 대 실측이(★기준 통일 = 복원 전) S2 +5↔15 · S3 +9↔11 · S4 0↔20 로 어긋났고, 어긋난 이유가 이 회차가 확정한 근인이다. ★단 재측정은 이 PR 이 --merge 로 착지한 «뒤»에 해야 의미가 있다 — merge-base 가 3296139 인 상태에서 잰 수라야 다음 회차의 실제 기준선이다. 지금 재면 또 낡은 수가 된다.", + "tradeoff": "⑴착지 후 재측정하면 정확하지만 별 회차가 하나 붙는다(git merge-tree 만 돌리면 되므로 XS 다). ⑵지금 브랜치 위에서 미리 재면 회차를 아끼지만, 게이트③이 --squash 로 착지시키면 그 수가 통째로 무효가 된다. ⑶재측정을 건너뛰면 S5 티켓이 「0충돌 물량」으로 발권되고 실제로는 설계 판단이 필요한 회차가 될 위험이 남는다.", + "effort": "XS — git merge-tree --write-tree --name-only 3회 + §5 표 갱신.", + "target": "docs/upstream-sync-approach.md §5" + } + ], + "resolvedIssues": [], + "adoptedProposals": [ + "2026-08-27-upstream-sync-s3#p0" + ], + "declinedProposals": [] +} diff --git a/docs/worklog/2026-08-27-upstream-sync-squash-convergence.md b/docs/worklog/2026-08-27-upstream-sync-squash-convergence.md new file mode 100644 index 00000000..23659c5f --- /dev/null +++ b/docs/worklog/2026-08-27-upstream-sync-squash-convergence.md @@ -0,0 +1,135 @@ +# 2026-08-27 — S1~S4 가 착지하고도 fork 가 upstream 에 가까워지지 않은 근인: 게이트③ `--squash` + +## 증상 + +S1~S4(PR #11·#13·#16·#17)가 **전건 `merged=true`** 인데도: + +| 축 | 값 (2026-08-27 재실측) | +|---|---| +| `git merge-base origin/main upstream/main` | **`62cf0c6a`** (2026-06-28 `Make ensure_initialized public`) = ★fork 시점 그대로 | +| S1~S4 컷(`1f356ae`·`af4f6f8`·`822504b`·`3296139`)이 `origin/main` 조상인가 | ★**4건 전부 «아니다»** (전건 `upstream/main` 조상임은 확인) | +| `git rev-list --count origin/main..upstream/main` | **33** | +| 착지 PR | #11(7커밋)·#13(10)·#16(15)·#17(21) — 전부 `merged=true` | +| 회차별 충돌 수 | ★**기준을 달지 않으면 비교 불가** — 아래 「회차별 충돌」 절 참조 | + +내용은 들어와 있다(`java_runtime/src/charset.rs`·`test_data/UnsupportedCharset.*` 실재). +**들어오지 않은 것은 계보다.** + +### 회차별 충돌 — ★**기준 명시** + +| 회차 | 그냥 재면(복원 전) | `-s ours` 복원 후 | 계보 상태 | ★재생분(앞 회차가 이미 닫은 자리) | +|---|---|---|---|---| +| S1 | **2** | — (**복원 불요** · fork 시점 base) | 해당 없음 | — | +| S2 | **15** | **5** | 끊김 → 복원함 | ★**15 중 10** | +| S3 | **11** | — (**복원 불요**) | ★**온전**(`merge-base` = `af4f6f8`) | ★**0** | +| S4 | **20** | **2** | 끊김 → 복원함 | ★**20 중 18** | + +★★**S3 는 «사례»가 아니라 «반례»다 — 계열에서 빼기만 하지 말고 이 문장을 남겨라.** +S3 는 S2 브랜치 «위에» 쌓아 `merge-base` 가 `af4f6f8` 로 **정상**이었고 `-s ours` 복원을 +**하지 않았다**(`s3.done.md` §0-⑴). 그 **11** 은 **계보가 온전한 상태의 순수 신규 충돌**이고 +**재생분 0** 이다. ⇒ 「계보 미수렴의 비용」 계열에 넣으면 **근거가 아니라 반증**이 된다. + +★**그래서 이 병의 근거는 «충돌 총수»가 아니라 «재생분»이다** — 기준이 하나이고 S3 도 자연히 설명된다: +★**S2 10 → S4 18**(둘 다 계보가 끊긴 회차 · 출처 `s2.done.md` 열거 · `s4.done.md` §2) · **S3 0**(계보 온전). + +★**초판은 「2 → 5 → 11 → 20」을 «우상향 계열»로 적고 «원장 기재»라고 표기했는데, 그 계열은 원장에 없다** — +네 수가 서로 다른 기준(S2 는 복원 «후», S4 는 복원 «전»)에서 하나씩 뽑혔고, +기준을 통일하면 복원후 **2·5·11·2** · 복원전 **2·15·11·20** 으로 ★**어느 쪽도 우상향이 아니다**. + +## 근인 확정 — 부모 수로 증명 + +``` +PR #11 merged=true pr_commits=7 merge_commit=6bfe97c4 parents=1 +PR #13 merged=true pr_commits=10 merge_commit=11ef5010 parents=1 +PR #16 merged=true pr_commits=15 merge_commit=4bb796de parents=1 +PR #17 merged=true pr_commits=21 merge_commit=3a597768 parents=1 +``` + +**4건 전부 부모 1개 = squash.** 7·10·15·21 커밋이 각각 1커밋으로 접혔다. + +### ★가설 반증 시도 — 실패했다(= 가설이 맞다) + +「브랜치를 cherry-pick 으로 만들어서 애초에 계보가 없었던 것 아닌가」를 직접 확인했다: + +``` +$ git cat-file -p 34a4235 | grep '^parent' +parent c80638a… # 우리 쪽 +parent 3296139… # ★upstream 컷 +``` + +`feat/rustjava-upstream-sync-s4` 브랜치의 `34a4235` 는 **부모 2개인 진짜 머지**이고 +`git merge-base --is-ancestor 3296139 feat/rustjava-upstream-sync-s4` → **YES**. +⇒ ★**브랜치는 계보를 갖고 있었고, 게이트③ `--squash` 가 그것을 버렸다.** 다른 설명은 없다. + +같은 브랜치의 `c80638a`(「chore: record upstream cut 822504b as merged (tree unchanged)」)는 +**S4 회차가 이미 손으로 `-s ours` 계보 기록을 시도한 흔적**인데, 그것 역시 같은 스쿼시에 함께 지워졌다. +⇒ ★**처방을 브랜치에 넣는 것만으로는 부족하다 — 게이트③이 스쿼시하는 한 매 회차 무효화된다.** + +## 규율 위치 (문구로 지목 · 줄번호 인용 금지) + +- `~/orchestrator/ORCHESTRATOR.md` — 「**제품 repo = `--squash`** / ★**orchestrator-ops = `--merge`(merge commit) · `--squash` 금지**」 (2곳) +- `~/orchestrator/templates/merge-ticket.tpl` — 「`gh pr merge -R --squash --subject "$SUBJ"` # 제품 repo(otterpebble·qts·wie·RustJava·dodu)」 + +★같은 템플릿이 이 병의 **형제 증상을 이미 알고 있다**: 「부모 PR 이 `--squash` 로 착지하면 +자식 PR 은 «공통 조상이 사라져» `add/add` 로 전건 충돌한다」. 우리 것은 그 upstream 판이다. + +## 처분 — **갈래 ⒞ (⒜+⒝)** + +### ⒜ 계보 기록 — 이 PR 이 집행한 것 + +`origin/main`(`3a59776`) 위에서 `git merge -s ours 3296139`. +`1f356ae`·`af4f6f8`·`822504b` 는 전부 `3296139` 의 조상이므로 **한 번의 머지가 네 컷을 모두 덮는다**. + +**트리 변경 0 증명 — 트리 SHA 동일**: + +``` +tree BEFORE (3a59776): c4f57d10bce2087cebe2e1156f716f6ba8f75335 +tree AFTER (c118d21): c4f57d10bce2087cebe2e1156f716f6ba8f75335 +git diff 3a59776..c118d21 → 0 lines +``` + +★`git diff --stat` 빈 출력은 `-s ours` 에서 정의상 항상 참이라 근거로 약하다(S4 워크로그의 교훈). +그래서 **트리 오브젝트 SHA 자체가 같음**을 근거로 쓴다 — 이쪽은 정의가 아니라 실물이다. + +**효과**: + +| 축 | 전 | 후 | +|---|---|---| +| `merge-base` vs `upstream/main` | `62cf0c6a` (2026-06-28) | **`3296139c`** (2026-07-19 `Add CLI classpath options (#184)`) | +| behind count | **33** | **18** | +| S1~S4 컷 조상 여부 | 0/4 | **4/4** | + +### ⒝ 앞으로의 upstream 동기 PR 은 `--merge` — ★**총괄 소관. 여기서 고치지 않았다** + +★**이 PR 자체가 `--squash` 로 착지하면 위 ⒜는 무의미하다** — 부모 2개가 1개로 접히면서 +`3296139` 조상 관계가 다시 사라지고, `merge-base` 는 `62cf0c6a` 로 되돌아간다. + +⇒ `ORCHESTRATOR.md`·`templates/merge-ticket.tpl` 의 제품 repo `--squash` 규율에 +**upstream 동기 PR 예외**를 넣을지는 **총괄이 판정한다**(REPORT.md 후속 추천 (1)). +★**wie 도 같은 함정 위에 있다 — behind 1067.** + +### ⒟(기각)를 택하지 않은 이유 + +기각하려면 다른 근인이 필요한데, 부모 수 4/4 = 1 과 브랜치 쪽 부모 2개가 **동시에** 성립하는 +설명은 스쿼시뿐이다. 브랜치 재작성·cherry-pick 가설은 위 반증 시도에서 실측으로 깨졌다. + +## 파급 — 잔여 회차 + +`docs/upstream-sync-approach.md` §5 표의 「새 충돌」은 **base 가 전진한다는 전제 위의 수**였다. +예측 대 실측 — ★**«복원 전» 으로 기준 통일**(계획서가 쓴 `merge-tree origin/main <컷>` 과 같은 기준): +S1 2↔**2** · S2 +5↔**15** · S3 +9↔**11** · S4 **0↔20**. +★**초판은 예측이 맞아 보이는 칸엔 «복원후»(S2 5), 무너져 보이는 칸엔 «복원전»(S4 20)을 짝지었다** — 그 짝을 폐기한다. +S4 는 **복원 전 20 · 복원 후 2** 로 ★**어느 쪽으로 읽어도 「0」 예측을 빗나갔다**. +§5 에 그 전제와 정정을 명시했다(S3 워크로그 `#p0` 제안 채택). + +남은 18커밋(S5 `c4665b0` · S6 `95ebc5c` · S7 `ba5797b`)은 **이 PR 이 `--merge` 로 착지한 뒤** +`merge-base` 가 `3296139` 인 상태에서 재측정해야 의미 있는 수가 나온다. + +## 하지 않은 것 + +- **머지 0** — PR 을 만들고 멈춘다(게이트②·③은 별 세션). +- **upstream 발신 0** · **force-push 0** · **history rewrite 0** · **`reset --hard` 0**. +- **제품 코드(`.rs`) 변경 0** — 이 회차는 계보와 규율만 다룬다. +- `~/orchestrator` 무접촉 — 규율 파일은 총괄 소관이라 손대지 않았다. +- `upstream/main` 헤드까지 `-s ours` 하지 **않았다**. S5~S7 내용은 실제로 없으므로 + 그것까지 계보를 얹으면 **거짓 주장**이 되고 남은 물량이 조용히 사라진다. `3296139` 까지만이 정확하다.