Skip to content
Merged
32 changes: 32 additions & 0 deletions REPORT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# REPORT
## [2026-09-17] base 를 당겼다 — ★**막고 있던 코드 충돌은 «이미 없었다»**(rustjava-adopt-link-stringconcatfactory-p1-fix2)
- 무엇을: `origin/main` 당김(뒤처짐 **9**) + 그 당김이 만든 `test-data/class-file-versions.txt` **3행**. ★제품 코드 **0줄** · 픽스처 바이트 **불변**.
- ★★**전제가 반증됐다**: 이 회차는 「`make_indy_fixtures.py` 4구역 코드 충돌」을 풀라고 발권됐는데, 지금 당기면 그 파일은 **충돌하지 않는다**. `-p1-fix` 회차가 **14:10 에 `0f06b93f` 로 이미 합집합 해소**했고 게이트②가 **15:43 에 그 head 를 approve** 했다 — 발권 근거였던 12:12 blocked 회신이 그 사이 **낡았다**.
- ★**재발도 불가능하다**(그냥 「지금은 없다」가 아니다): 뒤진 9커밋 중 `make_indy_fixtures.py` 를 만진 것이 **0건**이다.
- ★**그래도 합집합이 «진짜»인지 다시 쟀다** — 합집합의 전형적 실패는 「한쪽 의도가 조용히 빠지는 것」이라서다.
⒜생성기를 **실제로 돌려** 10개 픽스처가 전부 **바이트 불변**(양쪽 가족 — theirs `MakeConcat*` 3 · ours `RecipeWants*` 3 — 을 **한 생성기**가 낸다).
⒝★**양방향 개악**: ours 산출물 3개만 치우면 **ours 만 red**(theirs 2건 green) · theirs 3개만 치우면 **theirs 2건 red**(ours green). ⇒ 「선택」이 아니라 합집합이다.
- ★**남은 것은 충돌이 아니라 «부채»였다**: base 를 당기면 #57 의 버전 표가 들어오고 이 PR 의 픽스처 3개가 미등재라 `test_fixture_pins` 가 red 가 된다(게이트② 검수가 「`-merge` 회차가 표 3행을 함께 진다」고 이미 지목한 그것). 생성기로 채웠다 — ★**`3 0`(삭제 0)**.
- 원장 충돌 2건(`REPORT.md`·`STATE.md`)은 **합집합·시간순**. ★`STATE.md` 「진행중」 한 줄은 3-way 에서 **ours 가 이겼다**(base == theirs ⇒ 정상) — 결손이 아니다.
- 검증: `test_class_format` **16/0** · `test_fixture_pins` **3/0** · `cargo test --all` **578 / 0 / 1** · DoD **7명령 전건 rc=0**.

## [2026-09-17] 부트스트랩 정적 인자는 «적재 가능 상수»여야 한다 — 경계에서 «종류»로 (rustjava-adopt-bound-bootstrap-static-arguments-p0)
- 무엇을: 채택 제안 `2026-09-16-bound-bootstrap-static-arguments#p0`. ★**제품 동작이 바뀐다** — 인자가 적재 불가 상수를 가리키는 클래스 파일이 **`ClassFormatError`** 로 거부된다.
- 왜: JVMS 4.7.23 이 요구하는 것은 «인덱스가 어딘가에 닿는다»가 아니라 ★**「적재 가능 상수」**다(Integer·Float·Long·Double·Class·String·MethodHandle·MethodType·Dynamic).
Expand All @@ -13,6 +24,27 @@
- 검증: `cargo test --all` **575 passed / 0 failed / 1 ignored** · DoD 7명령 rc=0 · ★**새 픽스처 0**(기존 파일 바이트 패치 · 길이 필드 불변).
- ★**여기서 더 갈 수 없는 자리도 적는다**: `Dynamic` 인자의 서술자가 필드 서술자인지, `MethodHandle` 인자가 실제 멤버로 해석되는지는 **payload 가 필요**해 이 술어의 밖이다(설계이지 누락이 아니다).
- ★후속: `ClassFormatError` 에 **사유를 실어라**(M) — OpenJDK 는 인덱스와 이유를 말한다. ★p2-fix 회차가 낸 같은 제안과 **묶어서** 하는 편이 낫다.
## [2026-09-17] 레시피가 콜사이트와 어긋날 때 — ★**「싸고 옳다」는 두 겹으로 거짓이었다** (rustjava-adopt-link-stringconcatfactory-p1)
- 무엇을: 채택 제안 `2026-09-16-link-stringconcatfactory#p1`(「진단의 자리를 정하라」). ★**제품 동작이 바뀐다** —
레시피와 콜사이트의 «합의»를 **변환 전에 한 번** 재고, 없던 `java/lang/BootstrapMethodError` 를 런타임에 추가했다.
- 왜: 제안은 「현 런타임 검사는 싸고 옳다, 문제는 «자리»뿐」이라 했다. ★**둘 다 틀렸다.**
⑴★**그 가지는 애초에 던지지 못했다** — `java/lang/BootstrapMethodError` 가 이 런타임에 **없어서**
`jvm.rs:948` 의 unwrap 에서 **NoClassDefFoundError 로 패닉**했다. 이 형상의 픽스처가 **하나도 없어** 아무도 밟은 적이 없다.
⑵★**검사가 «부족분»만 봤다** — 레시피가 콜사이트보다 **짧으면** 남는 인자를 조용히 버리고 **틀린 문자열**(`a`)을 돌려주고 rc=0 이었다.
⇒ 부등호를 **상등**으로 바꾸고 인자·상수 두 축을 함께 잰다.
- 사용자 영향: 손상·수제 클래스 파일이 **패닉이나 조용한 오답 대신** `BootstrapMethodError` 를 받는다. 정상 javac 산출물은 **무영향**.
- ★★**제안의 처방(`classfile/validation.rs` 로 옮겨 `ClassFormatError`)은 기각한다 — 추측이 아니라 실측이다.**
OpenJDK 26.0.1 에 세 픽스처를 **직접 돌렸다**: 전건 `BootstrapMethodError`(원인 `StringConcatException`) · 프레임은 `linkCallSite` =
★**링크 시점**이고 ★**`ClassFormatError` 가 아니다**. 파일은 파싱되고, 부트스트랩 정적 인자의 «의미»는 클래스파일 형식의 소관이 아니다.
⇒ 제안이 스스로 적은 비용(「검증 단계에서 부트스트랩 인자를 걷는 것 = `attribute.rs` 가 일부러 피한 해결」)도 함께 면했다.
- ★**왜 «변환 전»인가**: 여기엔 `CallSite` 가 없어 링크가 첫 실행에 접힌다 ⇒ 그 순서에 가장 가까운 것이 「무엇도 변환하기 전에 잰다」이다.
먼저 변환하면 `String.valueOf` 를 통해 **사용자 `toString()` 이 돌고**, 그 예외가 이 진단을 덮는다.
- ★★**개악 4종 전건 red**(정상 574 green): M1 합의 검사 제거 · M2 `!=`→`>`(부족분만) · M3 상수 축 제거 ·
★**M4 `loader.rs` 에서 클래스 등록 제거 → `jvm.rs:948` 패닉이 «되살아난다»**(= 새 클래스가 하중을 진다).
- 검증: `cargo test --all` **573 → 574 passed / 0 failed / 1 ignored**(기준선은 `origin/main` 워크트리에서 실측) ·
DoD 7명령 rc=0 · 픽스처 재생성 **멱등**(기존 4개 바이트 불변).
- ★후속: **72개** `java/…Error|Exception` 이름이 이 워크스페이스의 오류 경로에 있고, 이번 회차 전까지 그중 **1개**(이 건)가 proto 없이 있었다.
★**지금 baseline 이 0** 이라 잠그기 가장 싼 시점이다 — `docs/worklog/2026-09-17-string-concat-recipe-arity.json`.

## [2026-09-17] `StringConcatFactory.makeConcat` 도 링크한다 — 단 «이유는 제안이 적은 것이 아니다» (rustjava-adopt-link-stringconcatfactory-p0)
- 무엇을: 레시피 없는 진입점 `makeConcat` 을 링크한다. ★**실행기 무접촉** — 콜사이트 인자 수로 **레시피를 합성**한다.
Expand Down
35 changes: 34 additions & 1 deletion STATE.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
# STATE

## 진행중
(없음 — 2026-09-16 실측: 착수 시 진행 티켓 0 · 열린 PR 0. ※「열린 PR 0」은 ★**이 회차 PR 착지 시점 기준**이다 — 회신 시점에는 그 PR 자신이 열려 있다)
(없음 — 2026-09-17 실측: 착수 시 열린 PR **4건**(#56·#57·#58·#59). ★**내 경로와 겹치는 것은 둘**이다 —
#59 가 `test-data/src/indy/make_indy_fixtures.py`·`tests/test_class_format.rs` · #56 이 `tests/test_class_format.rs`.
★`jvm-bytecode/src/interpreter.rs`·`rustjava-runtime/` 은 **겹침 0**. ⇒ 착지 순서는 **#56·#59 먼저, 이 PR 나중**이 맞다
(둘 다 이것보다 오래됐고 MERGEABLE/CONFLICTING 처분이 이미 걸려 있다). 겹침은 전부 **append 형 합집합**이라 해소는 기계적이다)

## 완료
- [rustjava-adopt-link-stringconcatfactory-p1-fix2] ★★**base 당김 — 그런데 막고 있던 코드 충돌은 «이미 없었다»(PR #60).**
★**전제 반증**: 「`make_indy_fixtures.py` 4구역 충돌」은 `-p1-fix` 가 **14:10 `0f06b93f`** 로 합집합 해소했고 게이트②가 **15:43 그 head 를 approve** 했다.
발권 근거(12:12 blocked 회신)가 그 사이 낡은 것이다. ★**재발 불가**도 확인 — 뒤진 9커밋 중 그 파일을 만진 것 **0건**.
★**합집합이 진짜인지 다시 쟀다**: 생성기 재실행이 10픽스처 **바이트 불변**(양쪽 가족을 한 생성기가 낸다) ·
★**양방향 개악** — ours 산출물만 치우면 ours 만 red · theirs 만 치우면 theirs 2건만 red ⇒ 「선택」이 아니다.
★**실제로 남아 있던 것은 부채**다: 당기면 #57 의 표가 들어와 이 PR 픽스처 3개가 미등재 ⇒ 생성기로 **3행**(★삭제 0) 등재.
★원장 2파일 합집합·시간순 · `--all` **578/0/1** · DoD 7명령 rc=0.
★★**게이트③ 착지 — PR #60 · `--merge`**(등재 repo `contracts/upstream-sync-repos.conf:22` · 티켓 `merge_strategy: merge` 선언분 ⇒ ★**계보 보존**).
게이트② **approve**(리니지 최신 회신 `…-p1-fix2.review.md`) · 핀 **`632c6b06`** ↔ 착수 시 PR head **동일**(불이동) ·
★**`MERGEABLE/CLEAN` · base 뒤처짐 «0»** ⇒ 충돌 해소·base 당김 **둘 다 불요**(앞 회차가 이미 당겼다).
★핀에서 `ci-presence` **rc=0 CI_GREEN** · 자식 PR **0건** · 배포 **0**(이 저장소에 배포 워크플로 없음) · 주기 자동 커밋 **0건** · 라이브 실행 주체 **없음**.
★**동봉은 이 기록 한 줄뿐** — 원장(worklog 쌍·`STATE`·`REPORT`)은 구현·승계 회차가 이미 실었다.
- [rustjava-adopt-bound-bootstrap-static-arguments-p0] ★★**부트스트랩 정적 인자 = «적재 가능 상수» — 경계에서 «종류»로.**
채택 제안 `2026-09-16-bound-bootstrap-static-arguments#p0`(worklog json `adoptedProposals` 기록). ★**제품 동작 변경 있음.**
★**제안이 적은 위험을 먼저 쟀다**(「틀리면 람다가 전부 corrupt」): 태그 검사는 payload 를 읽지 않으므로 `attribute.rs` 설계와 충돌하지 않고,
★**커밋된 클래스 파싱이 전/후 «144/12 동일»**(새로 거부 0). ★OpenJDK 26 은 같은 파일을 `ClassFormatError: argument_index 4 has bad constant type` 로 거부한다.
★**안 하면**: 링커에서 `UnsupportedOperationException` — 「파손」을 「미지원」이라 말하게 된다.
★개악 2종 red(존재만 되돌리기 · ★집합에 Utf8 한 칸 추가) · `--all` **575/0/1** · 새 픽스처 **0**(바이트 패치).
- [rustjava-adopt-link-stringconcatfactory-p1] ★★**레시피가 콜사이트와 어긋날 때 — 제안의 「싸고 옳다」가 두 겹으로 거짓이었다.**
채택 제안 `2026-09-16-link-stringconcatfactory#p1`(worklog json `adoptedProposals` 기록). ★**제품 동작 변경 있음.**
★**제안의 처방은 기각**(`classfile/validation.rs`/`ClassFormatError`) — ★**OpenJDK 26.0.1 에 픽스처 3종을 직접 돌린 실측**이 근거다:
전건 `BootstrapMethodError`(원인 `StringConcatException`) · 프레임 `linkCallSite` = **링크 시점** · **`ClassFormatError` 아님**.
★★**실제로 깨져 있던 둘**: ⑴`java/lang/BootstrapMethodError` 가 **런타임에 없어서** 그 가지가 던지는 대신
`jvm.rs:948` unwrap 에서 **패닉**했다(픽스처 0 이라 아무도 밟은 적이 없다) ⑵검사가 **부족분**만 봐서
레시피가 **짧으면** 남는 인자를 버리고 **틀린 문자열을 반환**했다(rc=0). ⇒ **상등 검사 · 변환 전 1회 · 인자/상수 두 축**.
★**변환 전인 이유**: `String.valueOf` 가 **사용자 `toString()`** 을 돌리므로, 먼저 변환하면 그 예외가 진단을 덮는다.
★**개악 4종 전건 red**(M1 검사 제거 · M2 `!=`→`>` · M3 상수 축 제거 · ★**M4 클래스 등록 제거 → 패닉 재현**) ·
`cargo test --all` **573 → 574 / 0 failed / 1 ignored**(기준선 워크트리 실측) · DoD 7명령 rc=0 · 픽스처 재생성 멱등.
★★**게이트③ 착지 — PR #60 · `--merge`**(등재 repo · `merge_strategy: merge` 선언분). 게이트② **approve** ·
핀 `532d98d7` **불이동**(착수 실측 2026-09-17T01:18:24Z) · ★**`MERGEABLE/CLEAN` · base 뒤처짐 «0»** ⇒ 충돌 해소·base 당김 **둘 다 불요**.
★핀에서 `ci-presence` **rc=0 CI_GREEN** · 자식 PR **0건** · 배포 **0**(이 저장소에 배포 워크플로 없음) · 주기 자동 커밋 **0건**.
★**동봉은 이 기록 한 줄뿐** — 원장(worklog 쌍·`STATE`·`REPORT`)은 구현 회차가 이미 실었다.
★★**착지 순서 고지 — 이 회차가 «새 `.class` 3개»를 들여온다**(`RecipeWants{MoreArguments,FewerArguments,AConstant}`).
열린 PR **#57**(ci-pending)이 「`.class` 는 `test-data/class-file-versions.txt` 에 등재돼야 하고 ★**미등재는 핀이 실패시킨다**」를 세운다
⇒ ★**이 PR 이 먼저 착지하면 #57 의 표에 이 셋이 «없어»** 그 회차가 red 가 된다(해소 = `python3 test-data/src/record-class-file-versions.py` 재생성).
★**텍스트 충돌이 0 이라 `mergeable` 로는 보이지 않는 종류다.**
- [rustjava-adopt-link-stringconcatfactory-p0] ★★**`StringConcatFactory.makeConcat` 도 링크한다 — 단 «이유는 제안이 적은 것이 아니다».**
채택 제안 `2026-09-16-link-stringconcatfactory#p0`(worklog json `adoptedProposals` 기록).
★**제품 동작 변경**: `makeConcat` 콜사이트가 **거부 대신 실행**된다. ★**실행기(`concat_with_constants`)는 한 줄도 안 바뀌었다.**
Expand Down
39 changes: 39 additions & 0 deletions docs/worklog/2026-09-17-base-pull-and-stale-block-premise.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"schema": "worklog/v1",
"date": "2026-09-17",
"taskId": "rustjava-adopt-link-stringconcatfactory-p1-fix2",
"summary": "This round was issued to union-resolve a four-region code conflict in test-data/src/indy/make_indy_fixtures.py that blocked PR #60's gate-3 merge. The conflict no longer exists: the -p1-fix round resolved it at 14:10 in 0f06b93f and gate 2 approved that head at 15:43, two hours after the blocked report the ticket was written from. What was actually left was a different thing wearing the same clothes — pulling base brings in PR #57's class-file-version table, and this PR's three fixtures are not in it, so the merged tree is red. Pulled base, backfilled the three rows, and re-verified that the existing union really is a union.",
"changes": [
"merge origin/main (9 behind): only REPORT.md and STATE.md conflicted; both are ledger files, resolved as a chronological union",
"test-data/class-file-versions.txt: +3 rows for RecipeWantsMoreArguments/FewerArguments/AConstant, written by the recorder"
],
"verification": [
"premise re-measured rather than trusted: `git merge origin/main` on the current head conflicts in REPORT.md and STATE.md only — make_indy_fixtures.py auto-merges",
"and it cannot come back from this base: of the 9 commits behind, zero touch test-data/src/indy/make_indy_fixtures.py",
"timeline established from commit times rather than from the ticket: blocked report 12:12 at head 38efee33 -> -p1-fix pushed 0f06b93f at 14:10 ('merge origin/main — resolved one code conflict as a union') -> gate 2 approved pin 0f06b93f at 15:43; 38efee33 is an ancestor of 0f06b93f",
"union is really a union, not a selection — both sides' entry points are present in one file: recipe_arity_call_site (ours, #60) at :108 and MAKECONCAT_DESCRIPTOR/make_concat_call_site (theirs, #59) at :156,:159, with both fixture families in the same table",
"generator re-run writes all 10 fixtures byte-identical (git reports no change), so one generator produces both sides' outputs unchanged",
"bidirectional mutation M1: move only ours' three RecipeWants*.class away -> test_a_recipe_that_contradicts_its_call_site_is_a_bootstrap_method_error red, both of theirs' tests green",
"bidirectional mutation M2: move only theirs' three MakeConcat*.class away -> test_the_recipe_free_factory_concatenates_every_argument and test_each_axis_of_the_factory_identity_is_observable red, ours green; restore leaves bytes unchanged",
"table backfill: git diff --numstat -> `3\t0` (zero deletions = no fixture was regenerated), and the three added paths are set-identical to the three .class files this PR adds",
"ledger conflict preservation proved line-wise both ways; the one line reported missing from the main side is STATE.md's 진행중 paragraph, which the 3-way merge correctly took from ours (base == theirs for that line)",
"cargo test --test test_class_format: 16 passed / 0 failed; test_fixture_pins: 3 passed / 0 failed; cargo test --all: 578 passed / 0 failed / 1 ignored",
"DoD all 7 commands rc=0"
],
"issues": [
"The ticket's stated head (38efee33) was two commits stale at dispatch. Nothing in the pipeline notices that a blocked report's premise was superseded by a later push to the same PR.",
"This round moves the head off the gate-2 pin 0f06b93f, so PR #60 needs re-review — unavoidable, since the base pull is what the merge round could not do itself (the version table is not a ledger file, so merge-template 2-c(a) does not authorise touching it)."
],
"adoptedProposals": [],
"proposals": [
{
"title": "Let a blocked report say which head it measured, and check that before re-issuing from it",
"plainSummary": "A round can be told to fix a problem that someone else already fixed two hours earlier, because the ticket is written from a report that has since gone stale.",
"userBenefit": "None directly. It stops a worker slot being spent re-deriving that there is nothing to do.",
"why": "The blocked report for PR #60 did record its head (38efee33) — correctly and prominently. The gap is that nothing compares that recorded head against the PR's current head at issuing time, so the ticket repeated a four-region conflict table that had already been resolved and approved. The comparison is one API call and the report already carries the input for it.",
"tradeoff": "It only catches the case where the head moved. A premise can also go stale because main moved underneath it — as happened here in the other direction, where pulling base created a new blocker that had nothing to do with the recorded conflict. So this would have caught this instance and would not catch the general problem, which risks reading as a fix when it is a narrowing.",
"effort": "S",
"target": "~/orchestrator (ticket issuing), not this repo"
}
]
}
Loading