Skip to content

Commit ef6ab75

Browse files
author
jun0
committed
[rustjava-count-symbols-dropped-from-second-parent-on-resolution] test(ci): 충돌 해소가 떨어뜨린 정의를 센다
채택 제안 2026-09-17-union-restores-silently-dropped-makeconcat#p0. 제품 코드 0줄 — 검사기 1개 + 의도 규약 + 기존 rust.yml 에 잡 하나(새 워크플로 파일 0). ⓑ 를 먼저 쟀다(이 게이트가 티켓을 끝낼 수 있었다): origin/main 에 손실을 재구성해 기존 축을 전부 돌렸더니 생성기 rc=0 · 단일결함 감사 rc=0(23) · test_class_format 21 · test_fixture_pins 3 · 파리티 rc=0 · git status 0줄 · 충돌 마커 0 ⇒ 아무것도 못 잡는다. ★왜 조용한지를 정확히 알아냈다: 손실은 떨어진 이름들이 서로 자기완결일 때만 조용하고, 충돌의 한쪽을 통째로 택하는 것이 바로 그 자기완결 슬라이스를 떨어뜨리는 것이다. ⓐ 제안의 「3건」을 다시 세니 4건이다(LINKED · MAKECONCAT_DESCRIPTOR · make_concat_call_site · method fieldref). ⓒ 넷째가 클래스 «안»의 메서드라 「최상위」만 보면 4 중 3만 잡는다 ⇒ 한 단계 중첩까지 본다. ⓓ 이 repo 는 --merge 착지지만 겨냥하는 것은 브랜치 내부 base 당김이라 착지 방식과 무관하다(부모 1개 범위 → 0 merge(s), rc=0). 의도/사고 분리는 커밋 trailer 다: Dropped-from-theirs: <name> -- <why>. 자기 커밋 하나만 면제하므로 「영구 목록이 검사기를 무력화한다」가 구조적으로 불가능하다. ★설계를 한 번 갈아엎었다: 초판의 「트리 == 부모1 이면 -s ours 로 보고 건너뛴다」 구조 면제가 개악 시험에서 theirs 를 통째로 버린 머지까지 삼켜 rc=0 이 됐다 — 그게 이 검사기의 존재 이유인 실패다. 면제를 선언(* 와일드카드)으로 교체했다. 양방향 전건: 개악 rc=1 + 이름을 말한다 · 정상 rc=0 · trailer rc=0, 지우면 rc=1 · 스쿼시 rc=0 · 실사고에서 손으로 찾은 것과 같은 분할(생성기 4 사고 + .rs 2 의도)을 기계가 낸다. 오탐을 세었다: 최근 200커밋·머지 83건 소급 → 8건 red · 정의 19개(9.6%) · 4.4초/머지. CI 는 origin/main..HEAD 만 보므로 과거 8건이 main 을 red 로 만들지 않는다. 잃는 것: 이름은 남고 본문이 비어진 경우는 못 잡는다 · 리네임·이동을 소실과 구별 못 한다 · trailer 는 우회로다 · DoD 블록에도 들어가 로컬 실행에도 시간이 붙는다. CI 잡을 늘렸으므로 CLAUDE.md DoD 블록도 같이 고쳤다 — 파리티 락이 명령 7개로 둘 다 일치를 답한다. cargo test --all 583/0/1(불변).
1 parent 8c7b473 commit ef6ab75

7 files changed

Lines changed: 349 additions & 0 deletions

File tree

‎.github/workflows/rust.yml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@ jobs:
6262
- uses: actions/checkout@v7
6363
- run: python3 scripts/check-worklog-json.py
6464

65+
# a conflict resolution that drops one side's work leaves no other trace: no marker, mergeable
66+
# CLEAN, suite green. fetch-depth 0 because the check needs the merge commits and their parents,
67+
# which a shallow checkout does not have. One runner, not the matrix.
68+
merge_drops:
69+
runs-on: ubuntu-latest
70+
steps:
71+
- uses: actions/checkout@v7
72+
with:
73+
fetch-depth: 0
74+
- run: python3 scripts/check-merge-dropped-symbols.py
75+
6576
# parity lock: the CLAUDE.md DoD block must reproduce this file's checks (see the script's
6677
# docstring — hand comparison failed five rounds in a row). One runner, not the matrix.
6778
dod_parity:

‎CLAUDE.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
cargo test --all
3232
python3 scripts/check-worklog-json.py
3333
python3 scripts/check-dod-ci-parity.py
34+
python3 scripts/check-merge-dropped-symbols.py
3435
```
3536
★★**이 블록은 이제 «기계가 지킨다» — `scripts/check-dod-ci-parity.py`(CI job `dod_parity`)가
3637
이 코드블록과 `rust.yml` 을 «각각 파싱해» 대칭차를 낸다.** 어긋나면 그 자리에서 red 다.

‎REPORT.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
# REPORT
2+
## [2026-09-18] 충돌 해소가 «한쪽 부모의 정의»를 떨어뜨렸는지 센다 — ★**개악 시험이 내 검사기의 구멍을 잡았다**(rustjava-count-symbols-dropped-from-second-parent-on-resolution)
3+
- 무엇을: 채택 제안 `2026-09-17-union-restores-silently-dropped-makeconcat#p0`(worklog json 기록). ★**제품 코드 0줄** — 검사기 1개 + 의도 규약 + 기존 `rust.yml` 에 잡 하나(**새 워크플로 파일 0**).
4+
- ★**ⓑ 를 먼저 쟀다**(이 게이트가 티켓을 끝낼 수 있었다): `origin/main` 에 손실을 **재구성**해 기존 축 전수 → 생성기 **rc=0** · 단일결함 감사 **rc=0**(23) · `test_class_format` **21** · `test_fixture_pins` **3** · 파리티 **rc=0** · `git status` **0줄** · 충돌 마커 **0** ⇒ ★**아무것도 못 잡는다.**
5+
★★**왜 조용한지를 정확히 알아냈다** — 손실은 **떨어진 이름들이 서로 자기완결일 때만** 조용하고(자기완결이 아니면 `AttributeError` 로 시끄럽게 죽는다), ★**충돌의 한쪽을 통째로 택하는 것이 바로 그 자기완결 슬라이스를 떨어뜨리는 것**이다.
6+
- ★**ⓐ 「3건」을 다시 세니 «4건»** — `LINKED`·`MAKECONCAT_DESCRIPTOR`·`def make_concat_call_site`·`method fieldref`(두 해소 커밋 각각).
7+
- ★**ⓒ 정의의 범위를 실제 4건에서 거꾸로 정했다**: ★넷째 `fieldref` 가 **`class Pool` 안의 메서드**라 ★**「최상위」만 보면 4 중 3만 잡는다** ⇒ **한 단계 중첩까지** 본다.
8+
- ★**ⓓ**: `rustjava` 는 등재 repo 라 `--merge` 착지(main 머지 **83/200**)지만 ★**겨냥하는 것은 «브랜치 내부 base 당김»**(사고 둘 다 그것 · 최근 **26건**)이라 **착지 방식과 무관**하다. 부모 1개 범위 → `0 merge(s)` **rc=0**.
9+
- ★**의도/사고 분리 = 커밋 trailer** `Dropped-from-theirs: <name> -- <why>` ⇒ ★**자기 커밋 하나만 면제**하므로 티켓이 경고한 「영구 목록이 검사기를 무력화한다」가 **구조적으로 불가능**하다.
10+
- ★★**설계를 한 번 갈아엎었다**: 초판의 «트리 == 부모1 이면 `-s ours` 로 보고 건너뛴다» 구조 면제가 ★**개악 시험에서 theirs 를 통째로 버린 머지까지 삼켜 rc=0** 이 됐다 — 그게 이 검사기의 존재 이유인 실패다 ⇒ **면제를 «선언»(`*` 와일드카드)으로 교체**. ★양방향을 안 돌렸으면 **끈 채로 착지**했다.
11+
- ★**합격선 양방향 전건**: ⒜개악 **rc=1 + 이름을 말한다** · ⒝정상 **rc=0** · ⒞trailer **rc=0**, 지우면 **rc=1** · ⓓ스쿼시 **rc=0** · ★실사고에서 **손으로 찾은 것과 같은 분할**(생성기 4 사고 + `.rs` 2 의도)을 기계가 낸다.
12+
- ★**오탐을 세었다**: 최근 200커밋·머지 83건 소급 → **8건 red · 정의 19개(9.6%)** · **4.4초/머지** ⇒ PR 당 내부 머지 0~2건이라 **실비용 ≈ 0~9초**. ★CI 는 `origin/main..HEAD` 만 보므로 **과거 8건이 main 을 red 로 만들지 않는다**.
13+
- ★**잃는 것**: ★**이름은 남고 본문이 비어진 경우는 못 잡는다** · 리네임·이동을 소실과 **구별 못 한다** · ★**trailer 는 우회로다**(사유 필수 + 커밋에 남는 것이 유일한 브레이크 — 더 조이지 않은 것이 판단이다) · 머지마다 4.4초가 붙고 **DoD 블록에도 들어가** 로컬에도 붙는다.
14+
- 검증: `cargo test --all` **583 passed / 0 failed / 1 ignored**(불변 — 제품 코드 무접촉) · `check-dod-ci-parity` → **「OK 두 축 모두 대칭차 0 — 명령 7개 · toolchain 2개로 «둘 다 일치»」**(★CI 잡을 늘렸으므로 DoD 블록도 같이 고쳤다).
15+
- ★후속 추천: ⑴소급 8건 중 upstream 동기(S5~S8) 가 떨어뜨린 `decode_str`·`encode_str` 이 «발견인가 수용된 판단인가»를 그 리니지가 판정(S) — 상세 = `docs/worklog/2026-09-18-merge-dropped-symbols-check.md`.
16+
217
## [2026-09-17] 코드 2파일 합집합 — ★**그런데 ours 의 «삭제»는 의도가 아니라 선행 머지의 «조용한 롤백»이었다** (rustjava-adopt-link-stringconcatfactory-p2-fix3)
318
- 무엇을: 게이트③이 `code-conflict-out-of-scope` 로 세운 PR #61 의 충돌 4파일(원장 2 + 코드 2)을 합집합으로 해소. ★제품 Rust **0줄**(테스트·픽스처 생성기만).
419
- ★★**브리프의 전제 하나가 반증됐다** — 「ours 가 «의도적으로» 지운 54·16줄을 되살리지 마라」였는데, 두 파일의 성격이 **정반대**였다:

‎STATE.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
(둘 다 이것보다 오래됐고 MERGEABLE/CONFLICTING 처분이 이미 걸려 있다). 겹침은 전부 **append 형 합집합**이라 해소는 기계적이다)
88

99
## 완료
10+
- [rustjava-count-symbols-dropped-from-second-parent-on-resolution] ★★**충돌 해소가 떨어뜨린 정의를 센다 — 그리고 개악 시험이 «내 검사기»의 구멍을 잡았다.** 채택 제안 `2026-09-17-union-restores-silently-dropped-makeconcat#p0`. ★**제품 코드 0줄**(검사기 + 규약 + 기존 워크플로 잡 1).
11+
★**ⓑ 를 먼저 쟀다** — 손실을 재구성해 기존 축 전수(생성기·단일결함 감사·두 스위트·파리티·`git status`·마커) ⇒ ★**전부 green = 아무것도 못 잡는다.** ★조용한 조건은 「떨어진 이름들이 **자기완결**일 때」이고, 한쪽을 통째로 택하는 것이 정확히 그것이다.
12+
★**ⓐ 3건 → «4건»** · ★**ⓒ** 넷째가 **클래스 «안»의 메서드**라 「최상위」만 보면 **4 중 3**만 잡는다 ⇒ 한 단계 중첩까지.
13+
★★**설계 교체**: 「트리==부모1 ⇒ `-s ours` 면제」가 **개악을 삼켜 rc=0** 이 됐다(그게 존재 이유인 실패다) ⇒ **선언(`*` trailer)으로 교체**.
14+
★양방향 전건: 개악 **rc=1 + 이름** · 정상 **rc=0** · trailer **rc=0**/지우면 **rc=1** · 스쿼시 **rc=0** · 실사고에서 **손 분할과 동일**(4 사고 + 2 의도).
15+
★**오탐 실측** 83머지 중 **8건(9.6%) · 19정의 · 4.4초/머지** ⇒ PR 당 **0~9초**. ★**잃는 것**: 본문만 빈 경우·리네임을 **못 가른다** · trailer 는 **우회로**다.
16+
★`--all` **583/0/1**(불변) · 파리티 **명령 7개 일치**(DoD 블록 동기).
1017
- [rustjava-adopt-link-stringconcatfactory-p2-fix2] ★★**#57 의 버전 표에 25행 등재 — 「착지 순서」가 만든 부채를 갚는다(PR #61).**
1118
★**막힌 것은 CI 도 충돌도 아니었다**: 핀 `85cf0fba` 에서 rc=0 CI_GREEN · `git merge origin/main` **코드 충돌 0** 인데
1219
★**합친 결과**가 #57 이 세운 「미등재 픽스처는 핀을 실패시킨다」를 어겼다(미등재 **25건** 재현).
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"date": "2026-09-18",
3+
"taskId": "rustjava-count-symbols-dropped-from-second-parent-on-resolution",
4+
"summary": "A check that reports definitions present in a merge's second parent and absent from its result, wired into the existing rust.yml. The hard part was not counting but separating accident from intent, which is recorded as a per-commit trailer rather than a file so it cannot outlive the merge it excuses. The first design had a structural exemption for merges whose tree equals their first parent's; the mandated mutation test showed that exemption also swallowed the exact failure the check exists for, so it was removed in favour of an explicit wildcard trailer.",
5+
"changes": [
6+
"scripts/check-merge-dropped-symbols.py: the check, with the intent trailer `Dropped-from-theirs: <name> -- <why>` and a `*` wildcard for merges that keep our tree on purpose",
7+
".github/workflows/rust.yml: a merge_drops job on the existing file (no new workflow), fetch-depth 0 because the check needs merge parents",
8+
"CLAUDE.md: the DoD block gains the same command — the parity lock requires CI and DoD to match, and it now reports 7 commands on both sides"
9+
],
10+
"verification": [
11+
"PRECONDITION b, the gate that could have ended this ticket: reconstructed the loss on origin/main and ran every existing axis against it. Dropping a self-consistent slice (recipe_arity_call_site + RECIPE_ARITY + its write loop) leaves the generator at rc=0, audit-fixture-single-defect.py at rc=0 (23 checks), test_class_format 21 passed, test_fixture_pins 3 passed, check-dod-ci-parity rc=0, git status empty, zero conflict markers. Nothing catches it, so the gate does not fire",
12+
"the same experiment with a NON-self-consistent slice does go red, but as an AttributeError crash rather than a diagnosis — which is why the silent case is the one that matters and had to be measured separately",
13+
"PRECONDITION a, recount: the two resolutions e53b2142 and 514d5b08 each dropped 4 definitions, not the 3 the proposal states — LINKED, MAKECONCAT_DESCRIPTOR, def make_concat_call_site, method fieldref",
14+
"MUTATION (a): a synthetic merge that discarded the other side's new function is rc=1 and names it — 'def kept_by_theirs — in the merged-in branch, not in the result'",
15+
"NORMAL (b): the same merge resolved as a union is rc=0",
16+
"INTENT (c): adding `Dropped-from-theirs: def kept_by_theirs -- superseded ...` makes it rc=0; removing the trailer returns it to rc=1; the `*` wildcard form is rc=0",
17+
"SQUASH (d): a one-parent commit range reports '0 merge(s)' and rc=0",
18+
"run against the real incident, the check reproduces by machine the same split the -fix3 round found by hand: 4 accidental drops in the generator plus 2 intentional test-function removals in tests/test_class_format.rs",
19+
"cargo test --all: 583 passed / 0 failed / 1 ignored, unchanged — no product code touched",
20+
"check-dod-ci-parity: OK, 7 commands and 2 toolchains matching on both sides after the wiring"
21+
],
22+
"issues": [
23+
"FALSE POSITIVE RATE, measured not guessed: over the last 200 commits of origin/main (83 merges) the check flags 8 merges / 19 definitions, about 9.6% of merges, and takes 4.4s per merge (368s for 83). The first and final designs give the same count. A pull request usually contains 0-2 internal merges, so the per-PR cost is roughly 0-9s. CI only walks origin/main..HEAD, so those historical merges are never re-examined and do not turn main red.",
24+
"WHAT IT CANNOT SEE: a definition whose name survives while its body is emptied or gutted. The check compares name sets, so 'still there but no longer does anything' passes. It also cannot tell a genuine rename from a drop-plus-add, and a move between files reads as a drop in the old file.",
25+
"THE TRAILER IS A BYPASS, and deliberately so: anyone in a hurry can write one and move on. The only brake is that a reason is required by the format and the trailer is visible in the merge commit. That is weaker than a gate and stronger than nothing; making it harder was judged over-design for a check with no track record yet.",
26+
"Two of the flagged historical merges are the known incident. The rest are upstream-sync merges (S5-S8) and one `-s ours` ancestry record. The sync ones genuinely dropped upstream methods — decode_str and encode_str are absent from the whole result tree, not moved, which was checked — so whether they are findings or accepted judgements is for the sync lineage to say, not this round."
27+
],
28+
"adoptedProposals": [
29+
"2026-09-17-union-restores-silently-dropped-makeconcat#p0"
30+
]
31+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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

Comments
 (0)