diff --git a/REPORT.md b/REPORT.md index 2956f63c..ada7328f 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,4 +1,15 @@ # REPORT +## [2026-09-19] 검사기가 «실행마다 자기와 불일치»했다 — `sorted()` 한 줄(2026-09-19-partial-clone-blob-vs-absence-p0) +- 무엇을: 채택 제안 `2026-09-19-partial-clone-blob-vs-absence#p0`(worklog json 기록). `check-merge-dropped-symbols.py` 의 **경로 루프**를 정렬한다. ★**1파일 +7/−1**(코드 1줄 + 측정 기록 주석 6줄). +- ★**전제 재측**(오늘 `origin/main` · 같은 범위 · `PYTHONHASHSEED=random` **8회**): ★**순서 2종**(6 + 2) — 같은 **6건**을 다른 차례로 찍는다. ⇒ 제안은 옳고, ★**그 이유도 옳다**: 이 리니지가 실제로 그 대가를 치렀다(전/후 diff 가 «회귀»처럼 보였고, **원판본이 자기와 불일치**함을 보여서야 갈렸다). +- ★★**다만 «자리»는 제안이 지목한 곳이 아니다**: 제안은 「set 에 모아 iteration order 로 «찍는다»」고 했는데 ★**찍는 자리는 이미 정렬돼 있었다**(`sorted(theirs_symbols - …)` ⇒ 경로 «안»의 이름은 늘 정렬). 흔들린 것은 ★**바깥 경로 루프** — `for path in filter(None, changed)` 이고 `changed` 는 `git diff --name-only` 둘을 합친 **set** 이다. ⇒ 고친 자리는 **print 가 아니라 루프**다. +- ★**양방향 축(제품 스크립트)**: 그 줄만 되돌리면 8회에 **순서 2종** ↔ 넣으면 **8/8 동일**. ★**내용은 양방향 불변** — findings **6** · **rc=1** · 요약 줄 동일. + ★**Acceptance 문면 고지**: 「되돌리면 red」를 요구하는데 ★**이 변경에는 뒤집을 판정이 없다**(순서만 바꾸고 rc 는 설계상 전후 1) ⇒ 축을 **«순서 종수»**로 세웠고 그 사실을 적는다. +- ★**대가**: ⒜보고가 **순회 순서를 더는 반영하지 않는다**(아무도 안 읽어서 안전했고, 그래서 여태 안 들켰다) ⒝★**«보고를 비교 가능»하게 할 뿐 «검사를 결정적»으로 만들지는 않는다** — `examined` 수·등장 머지 등 저장소 상태 의존은 그대로다(과장 금지) ⒞★**이 검사기만** 고쳤다 — 형제 검사기는 **보기엔** 안전했으나(정렬된 missing 목록·정렬된 파일 순회) **재지는 않았다**. +- ★**비용 유의차 없음**: 전 **2.09/2.51/2.41s** ↔ 후 **2.52/1.90/1.65s**(구간 겹침). +- ★**노력도**: 제안 추정 **S** 가 맞았다 — 실제 일은 **측정**(8회 × 2방향)이었고, 그것이 「정렬해야 한다」를 「정렬 안 돼 있었고 그 크기는 이만큼」으로 바꾼다. +- 검증: DoD 9명령 · 아래 절. + ## [2026-09-19] 적재 가능 집합을 «로더에서 읽을까» — ★**아니다, 재유도를 유지한다**(rustjava-loadable-set-from-loader-vs-rederive-decision) - 무엇을: 채택 제안 `2026-09-18-named-exception-classes-are-loadable#p2`(worklog json 기록). ★**순수 결정 회차 — `.rs` 0줄 · `scripts/` 0줄.** 산출 = `docs/loadable-set-source-of-truth.md`(선례 = `docs/test-data-target-policy.md`). - ★★**전제부터 확인했다 — 「4결함 중 3이 재유도」는 «참»이다.** 산문이 아니라 **고침 커밋 `89c2e83c` 에서** 갈랐다: ⑴`as_proto` 전용 → `list_proto` 3건 누락 ⑵한 `impl` 의 첫 `name:` 오귀속 ⑶짧은 이름 키 충돌 = **재유도(loadable) 3건** · ⑷줄 단위 스캔이 rustfmt 가 쪼갠 34건 누락 = ★**호출부 스캔(named) 1건**. diff --git a/STATE.md b/STATE.md index 04340f30..1e51a361 100644 --- a/STATE.md +++ b/STATE.md @@ -7,6 +7,11 @@ (둘 다 이것보다 오래됐고 MERGEABLE/CONFLICTING 처분이 이미 걸려 있다). 겹침은 전부 **append 형 합집합**이라 해소는 기계적이다) ## 완료 +- [2026-09-19-partial-clone-blob-vs-absence-p0] ★★**검사기가 실행마다 자기와 불일치했다 — 경로 루프 `sorted()` 한 줄.** 채택 제안 `2026-09-19-partial-clone-blob-vs-absence#p0`. ★**1파일 +7/−1.** + ★**전제 재측**: 오늘 main 에서 8회에 **순서 2종**(6+2) · 같은 6건. + ★★**자리가 제안과 다르다**: 「print 가 set 순서」가 아니라 ★**바깥 «경로» 루프**가 set 이었다(이름은 이미 `sorted`). + ★**양방향**: 되돌리면 **2종** ↔ 넣으면 **8/8 동일** · 내용 불변(6건 · rc=1 · 요약 동일). ★rc 로는 red 를 못 만든다(순서만 바뀐다) — 축을 «순서 종수»로 세웠다. + ★**대가**: 순회 순서 미반영 · ★**«비교 가능»일 뿐 «결정적»이 아니다**(저장소 상태 의존 그대로) · **이 검사기만** 고쳤다(형제는 미측정). - [rustjava-loadable-set-from-loader-vs-rederive-decision] ★★**적재 가능 집합은 «재유도»를 유지한다 — 로더에서 읽지 않는다.** 채택 제안 `2026-09-18-named-exception-classes-are-loadable#p2`. ★**순수 결정 · 코드 0줄** · 산출 = `docs/loadable-set-source-of-truth.md`. ★**전제 확인**: 「4중 3이 재유도」는 **참**(고침 커밋 `89c2e83c` 에서 갈랐다 — loadable 3 · named 1). ★★**그 1건이 결정한다**: `named`(코드가 무엇을 넘기는가)는 **로더가 답할 수 없다** ⇒ 로더 읽기는 **파서 하나를 없앨 뿐 파싱을 못 없앤다**(그 절반에서 형제 회차가 ★**4시간 31분** 뒤에 또 잡았다 — ★**「다른 함수 8종 41자리」**를 쓸어담는 앵커 함정이고, 세면 **33** · 맞는 답은 **0** 이다). diff --git a/docs/worklog/2026-09-19-sorted-findings.json b/docs/worklog/2026-09-19-sorted-findings.json new file mode 100644 index 00000000..cc2d3d58 --- /dev/null +++ b/docs/worklog/2026-09-19-sorted-findings.json @@ -0,0 +1,37 @@ +{ + "date": "2026-09-19", + "taskId": "2026-09-19-partial-clone-blob-vs-absence-p0", + "summary": "The merge-dropped-symbols check emitted its findings in hash-seed order, so two runs of the same version disagreed. Re-measured on today's origin/main: eight runs produced two distinct orderings of the same six findings. Sorting the path loop makes the report comparable; content, rc and summary are unchanged.", + "measurements": { + "runs_per_direction": 8, + "orderings_before": 2, + "orderings_after": 1, + "findings": 6, + "rc_before": 1, + "rc_after": 1, + "files_changed": 1, + "lines_added": 7, + "lines_removed": 1, + "runtime_before_seconds": [2.09, 2.51, 2.41], + "runtime_after_seconds": [2.52, 1.90, 1.65] + }, + "verification": [ + "premise re-measured on origin/main @ ad9eb1a6: 8 runs under PYTHONHASHSEED=random over e53b2142^..e53b2142 gave 2 orderings (6 + 2) of the same 6 findings", + "axis, both directions on the product script: with the sort reverted 8 runs give 2 orderings; with it, 8/8 identical", + "content unchanged in both directions: 6 findings, rc=1, identical summary line", + "cost: before 2.09/2.51/2.41s vs after 2.52/1.90/1.65s, overlapping" + ], + "changes": [ + "scripts/check-merge-dropped-symbols.py - the path loop iterates sorted(changed) instead of the set, with the measurement recorded in a comment", + "docs/worklog/2026-09-19-sorted-findings.{md,json}, REPORT.md, STATE.md" + ], + "issues": [ + "The proposal located the problem at the print site ('accumulated in a set and printed in iteration order'); names were already sorted there. The unordered part was the outer path loop over a set built from two git diff --name-only results.", + "This makes the report comparable, not the check deterministic: examined counts and which merges appear still depend on repository state.", + "Only this checker was changed. The sibling check appeared safe on inspection (sorted missing list, sorted file walk) but was not measured." + ], + "adoptedProposals": [ + "2026-09-19-partial-clone-blob-vs-absence#p0" + ], + "proposals": [] +} diff --git a/docs/worklog/2026-09-19-sorted-findings.md b/docs/worklog/2026-09-19-sorted-findings.md new file mode 100644 index 00000000..fdff8013 --- /dev/null +++ b/docs/worklog/2026-09-19-sorted-findings.md @@ -0,0 +1,68 @@ +# 2026-09-19 — The check disagreed with itself between runs. One `sorted()` fixed it. + +Round: `2026-09-19-partial-clone-blob-vs-absence-p0` +Adopted proposal: `2026-09-19-partial-clone-blob-vs-absence#p0` — +*"Sort the checker's findings so two runs can be compared."* + +## The premise, re-measured on today's `origin/main` + +Eight runs of the **unchanged** checker over the same range (`e53b2142^..e53b2142`), under +`PYTHONHASHSEED=random`, hashing the output: + +``` +6 16efef4228f50b1147cb57f8e1aced09 +2 9b1105a77918f30b31c42c890815d10c +``` + +Two orderings of the same six findings. The proposal is right, and it is right for the reason it +gives: this cost a real round — a before/after diff of this checker looked like a regression until +the *unchanged* version was shown to disagree with itself. + +## Where it came from, which is not quite where the proposal looked + +The proposal says *"Findings are accumulated in a set and printed in iteration order."* Close, but +the print site was already fine: names are emitted through `sorted(theirs_symbols - …)`, so findings +**within a path** were always ordered. What was unordered is the **path loop** — +`for path in filter(None, changed)`, where `changed` is a `set` built from two `git diff --name-only` +results. So the fix is on the outer loop, not at the print: + +```python +for path in sorted(filter(None, changed)): +``` + +**Changed: 1 file, +7/−1 lines** (one of them code, six a comment recording the measurement). + +## Axis — bidirectional, on the product script + +| form | 8 runs, `PYTHONHASHSEED=random` | +|---|---| +| that line reverted | **2 orderings** (6 + 2) | +| with it | **1 ordering** (8 / 8) | + +Content is untouched in both directions: 6 findings, `rc=1`, and the same summary line +`8 merge(s) in e53b2142^..e53b2142: 6 definition(s) dropped without a trailer`. + +**On the acceptance wording**: it asks for "revert it and get red". This change has no verdict to +flip — it changes output *order*, not outcome, so `rc` is 1 before and after by design. The axis is +therefore the ordering count above, measured in both directions on the real script. + +## What this costs + +- **The report no longer reflects traversal order.** Nothing reads it, which is exactly why this was + safe — and also why it was never noticed. +- **It makes the report comparable, not the check deterministic.** `examined` counts, which merges + appear, and everything else that depends on repository state still vary with the repository. A + future round that diffs two runs across *different* trees will still see real differences; this + only removes the false ones. +- **It fixes this checker only.** The sibling checks were not audited for the same shape in this + round; `check-named-exception-classes-are-loadable.py` emits its missing list through `sorted(…)` + and walks files through a sorted `rust_files()`, so it appeared safe, but that is an observation + in passing rather than a measurement. +- Runtime: **no significant change** — before 2.09 / 2.51 / 2.41 s, after 2.52 / 1.90 / 1.65 s + (overlapping). Sorting a set of a few dozen paths is not where this check spends its time. + +## Effort + +The proposal estimated **S** and that was right: the change is one line. The round's actual work was +the measurement — 8 runs × 2 directions — which is what turns "should be sorted" into "was unordered, +here by how much". diff --git a/scripts/check-merge-dropped-symbols.py b/scripts/check-merge-dropped-symbols.py index d038145a..af4a28ae 100755 --- a/scripts/check-merge-dropped-symbols.py +++ b/scripts/check-merge-dropped-symbols.py @@ -243,7 +243,13 @@ def check(merge): accounted = excused(merge) if excuses_everything(accounted): return [], 0 - for path in filter(None, changed): + # Sorted because `changed` is a set: without this the findings come out in whatever order the + # hash seed produced, and two runs of the *same* version disagree. Measured on `origin/main` + # before this line: eight runs under PYTHONHASHSEED=random gave two distinct orderings of the + # same six findings (6 + 2). That cost a real round -- a before/after diff of this check looked + # like a regression until the unchanged version was shown to disagree with itself. Names are + # already sorted within a path below; this makes the whole report comparable. + for path in sorted(filter(None, changed)): theirs_symbols = symbols(theirs, path) if theirs_symbols is None: continue