diff --git a/CLAUDE.md b/CLAUDE.md index d473748..40f16a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,8 +22,28 @@ **검증기로 막을 수 없다.** ⇒ 이 조항은 **1차 방어**이며 **최종 방어는 diff 검토**다. ## Definition of Done -- `cargo fmt --check` · `cargo clippy` · `cargo test` · `python3 scripts/check-worklog-json.py` - 전부 green(명령 상세 = `AGENTS.md`). +- ★**`.github/workflows/rust.yml` 이 «치는 그대로» 전부 green** — 축약하지 말고 이 여섯 줄을 그대로 쳐라: + ``` + cargo fmt --all -- --check + cargo clippy --all -- -D warnings + cargo +beta clippy --all -- -D warnings + cargo clippy --workspace --exclude test-utils --target wasm32-unknown-unknown -- -D warnings + cargo test --all + python3 scripts/check-worklog-json.py + ``` + ★★**`+beta` 줄을 빼지 마라 — CI 는 이 검사들을 «6셀»(toolchain 2 × OS 3)로 친다.** + `rust.yml` 의 `strategy.matrix.rust = [stable, beta]` 가 그 차원이고, 이 repo 엔 `rust-toolchain.toml` 이 + **없어** 맨 `cargo` 는 **stable 1개**로만 돈다. ⇒ ★**toolchain 축을 안 적으면 「rust.yml 이 치는 그대로」가 «거짓»이 된다.** + ★**실측 근거**(2026-09-04): `#[allow(clippy::double_must_use)]` **9곳을 전건 지우면** + ★**stable clippy 는 0인데 beta clippy 는 RED**(★**rc=101** · `double_must_use` 진단 **6**건 — + 전건 `jvm/src/jvm.rs:140·404·438·779·791·1016` · cargo 요약 줄이 「due to **6** previous errors」라고 말한다) + ⇒ ★**그 자산의 그물은 «beta 축»에만 있다.** + ★★**축약본도 쓰지 마라 — 그것이 2026-09-04 에 실제로 구멍을 만들었다.** 종전 문안은 + `cargo fmt --check`·`cargo clippy`·`cargo test` 로 줄여 적어 ★**wasm32 줄이 통째로 빠져 있었고**, + 그 줄이 ★**`--exclude <크레이트 이름>` 이 사는 «유일한» 자리**다. ⇒ upstream 이 `test_utils` 를 + `test-utils` 로 개명(S8)했을 때 **로컬에서는 어떤 명령으로도 드러나지 않고 CI 에서만** 빨개졌다. + ⇒ ★**CI 가 검사를 늘리거나 «매트릭스 차원»을 바꾸면 이 블록도 «같이» 고쳐라**(아래 재개 조건이 둘 다 센다). + ※OS 축(3종)은 로컬에서 재현할 수 없다 — ★**그 차원만은 CI 가 유일한 그물이고, 그것은 «알고 두는» 값이다.** - 착수·완료마다 STATE.md 의 "진행중/완료/다음" 을 갱신하고, 완료 시 REPORT.md 상단에 `[YYYY-MM-DD]` 요약 3줄(무엇을·왜·사용자 영향) + 후속 추천을 append 한다. - ★**후속 추천을 적었으면 `docs/worklog/YYYY-MM-DD-.{md,json}` 한 쌍도 남긴다** — diff --git a/REPORT.md b/REPORT.md index 1d34874..b6d07cf 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,5 +1,48 @@ # REPORT +## [2026-09-04] 「우리 자산이 낡는다」 상시 조항 판정 — 넣지 않고 «구멍 하나»를 막았다 (rustjava-sync-contract-standing-clause-for-our-assets-going-stale) +- 무엇을: 운영자 채택 제안 ★**셋**(`2026-09-04-upstream-sync-s6#p1` · `…-s7#p1` · ★`…-s8#p0`)에 대한 **결정**이다. + (★**[fix3 정정] 초판은 「둘」** — 같은 제안의 세 번째 판이 도착해 `adoptedProposals` 는 **세 ref** 다.) + ★**결론: 상시 조항을 «넣지 않는다».** 대신 ★**로컬 DoD 가 CI 의 «매트릭스»를 재현하지 않던 것**을 고쳤다 — + `CLAUDE.md` DoD 가 이제 **CI 명령 5줄 + toolchain 축 1줄 = «6줄»**을 축약 없이 싣는다. + ★**코드(`.rs`) 변경 0** · 규범 문서 **2파일**(`CLAUDE.md` · `docs/upstream-sync-approach.md`) + 기록 문서 4 + (`REPORT.md` · `STATE.md` · 워크로그 `.md`/`.json`). + ★**[게이트② 정정] 초판은 「CI 검사 5종 중 wasm32 clippy 한 줄」이라 적었다** — 계수 «1» 시절 문면이고, + 계수 **2** 정정 후에는 **두 축**(target · toolchain)이라 DoD 도 **6줄**이다. +- 왜: ★**목록 문서화로 시작하지 않고 «먼저 셌다»**(티켓이 그렇게 요구했다). 우리 자산 **8건**을 + ★**돌연변이로 깨뜨려** 무엇이 잡는지 실측했다 — 경로 문자열·`setProperty` 서술자·charset 라우팅· + `ClassFormatError` 종류 단정은 **`cargo test` RED**, 수동 span 은 **clippy RED**, + `double_must_use` allow 는 **깨져도 무해**, 워크로그 스크립트는 **로컬 DoD 와 CI 둘 다** 잡는다. + ⇒ ★★**[게이트② `request-changes` 정정] 「아무것도 없음」은 «2개»다**(② CI `--exclude` · ⑦ `double_must_use` allow). + 초판이 ⑦을 «비하중» 자리에서 재 「무해」로 적었으나, ★**9곳 전건 삭제로 재측정하면 stable 0 · ★beta RED**(rc=101 · 진단 **6**건) + ⇒ ⑦의 그물도 **CI 만**이다. ⇒ ★**「1개면 그 하나를 고쳐라」 규칙은 적용되지 않는다.** + ★★**게다가 그 1개도 «조용히» 실패하지 않는다** — cargo 가 `warning: excluded package(s) … not found` + 를 찍고 빌드가 깨진다 ⇒ ★**문제는 «침묵»이 아니라 «늦음»**(push 후 CI 에서만)이고, + ★**근인은 「자산 목록이 없다」가 아니라 «로컬 DoD 가 CI «매트릭스»를 재현하지 않는다»** 였다 — + ②는 빠진 `- run:` 줄(**target** 축) · ⑦은 빠진 **toolchain** 축(beta) ⇒ ★**한 근인의 두 얼굴**이다. + ⇒ ★**결론은 그대로 「넣지 않는다」이나 논거가 바뀌었다**: 근인 하나를 고치면 둘 다 그물을 얻는다 + (DoD 에 `cargo +beta clippy` 를 넣자 ⑦이 **로컬에서 RED**(rc=101 · 진단 **6**건)로 잡힌다 · 실측). +- 사용자 영향: 런타임 동작 **무변경**. 회차가 로컬에서 CI 와 **같은 6줄**(★**toolchain 축 포함**)을 돌리게 되어 + ★**「로컬 green 인데 CI red」 부류가 이 축에서 사라진다**(S5·S7·S8 이 실제로 그 부류였다). +- 검증: **DoD 6줄을 문면 그대로** 실행 — 전건 **rc=0**(★`cargo +beta clippy --all -- -D warnings` **rc=0** 포함) · + `cargo test --all` **554 / 0 / 1**(baseline 동수 · 새 red 0). ★**돌연변이는 전부 복구**했고 `.rs` 변경은 **0**이다. +- ★★**사료 — 「세 번」·「두 방향」을 확정하되 결론은 «한 조항으로 못 덮는다»**: + S3(문구 단정 3건 · 테스트) · S5(io 5곳 · 테스트) · S6(regex 3곳 · 정독) = **⑴신규/판본 교체** · + S7(우리 테스트 5곳 · **컴파일**) = **⑵공용 API 변경** · S8(`rust.yml`·경로 4곳 · **CI만/테스트**) = **⑶개명**. + ⇒ ★**잡는 그물이 각각 다르므로**, 조항 하나로 덮으면 **이미 그물이 있는 여섯 자리에까지 사람 확인을 얹게 된다** + (★**세는 법**: 돌연변이 표 **8행** − 그물이 「CI 만」인 **2행**(② · ⑦) = **6** · 명령은 `docs/upstream-sync-approach.md` §4. + ★**[게이트② 정정] 초판의 「다섯」은 계수 «1» 시절의 파생 수였다** — 계수가 2 가 되며 **6**이 맞다). +- ★★**재개 조건**(결정에 재개 조건이 없으면 «영구 종결»로 읽힌다): + 「**CI 가 치는 검사 중 로컬 DoD 에 없는 것이 1건이라도 생기면 다시 연다**」 · + ★**세는 명령을 `docs/upstream-sync-approach.md` §4 에 박았고, 정정으로 «축이 둘»이 됐다** + (축① `- run:` 줄 · ★축② **toolchain 매트릭스**) · ★**오늘의 값 = 축① 0 · 축② 0**(착수 시 축① 5 · 축② 1). + ★세 돌연변이로 **각 축이 «자기 자리»에서만 반응**함을 실측했다. +- ★★**[정정] 초판 후속 추천 3번을 «철회한다»** — 「`double_must_use` allow 가 이제 지워도 통과한다」는 + **비하중 돌연변이에서 나온 거짓**이다. ★**9곳을 전건 지우면 beta clippy 가 RED 다**(rc=101 · 진단 **6**건) ⇒ ★**지우지 마라.** +- 후속 추천: ⑴**게이트③은 `--merge`**(등재 repo). ⑵★**upstream 동기 «정기 축» 판정은 아직 열려 있다** + (S8 워크로그 `proposals[1]` · behind 0 인 지금이 적기다) — 이 회차는 그것을 «건드리지 않았다». + ⑶★**C7 고지**: DoD 블록 «개악»과 **OS 축 3종**은 이 대조가 못 잡는다 — 회차가 두 축을 «실제로 돌렸는지» 적어라. + ## [2026-09-04] upstream 동기 S8 — 컷 `bd42427` 개명 스윕 · ★**behind 0** (rustjava-upstream-sync-s8-rename-sweep-decision) - 무엇을: upstream `bd42427` 까지 **12커밋**(crates.io 공개 준비 개명 스윕)을 `--merge` 로 흡수하고 충돌 **8건**을 해소했다. ★★**`merge-base` `ba5797b` → `bd42427` · behind `12` → ★`0`** · 부모 **2개** diff --git a/STATE.md b/STATE.md index bd17a9b..465d4b6 100644 --- a/STATE.md +++ b/STATE.md @@ -23,6 +23,45 @@ (25번째 run, 앞선 24건 전부 red). **PR 대기 — 게이트③ 미착지.** ## 완료 +- [rustjava-sync-contract-standing-clause-for-our-assets-going-stale] ★**「우리 자산이 낡는다」 상시 조항 + «판정» — 결론: ★★넣지 «않는다». 대신 «구멍 하나»(로컬 DoD 가 CI 매트릭스를 재현하지 않던 것)를 막았다.** + ★코드(`.rs`) 변경 0 · 규범 문서 2 + 기록 문서 4. + ★**먼저 셌다(목록 문서화로 시작하지 않았다)**: 자산 8건을 **돌연변이로 깨뜨려** 무엇이 잡는지 쟀다 — + 경로 문자열·`setProperty` 서술자·charset 라우팅·`ClassFormatError` 단정은 **`cargo test` RED** · + 수동 span 은 **clippy RED** · `double_must_use` allow 는 **깨져도 무해** · 워크로그 스크립트는 **로컬+CI 둘 다**. + ⇒ ★★**[게이트② 정정] 「아무것도 없음」은 «1개»가 아니라 «2개»**(② CI `--exclude` · ⑦ `double_must_use` allow) — + 초판이 ⑦을 «비하중» 자리(중복 crate-level attribute 1곳)에서 재 「무해」로 적었고, ★**9곳 전건 삭제로 재측정하면 + stable 0 · ★beta RED**(rc=101 · 진단 **6**건)이라 ⑦의 그물도 **CI 만**이다. ⇒ 「1개면 그 하나를 고쳐라」 규칙은 **적용되지 않는다**. + ★**결론은 그대로 「넣지 않는다」이나 논거를 다시 세웠다**: ②와 ⑦은 «한 근인의 두 얼굴»이다 — + ★**로컬 DoD 가 CI «매트릭스»를 재현하지 않는다**(② = 빠진 `- run:` 줄 = target 축 · ⑦ = 빠진 toolchain 축). + ⇒ 근인 하나를 고치니 둘 다 그물을 얻었다(DoD 에 `cargo +beta clippy` 추가 ⇒ ⑦이 **로컬에서 RED**(rc=101 · 진단 **6**건)로 잡힌다). + ★★**근인은 「자산 목록이 없다」가 아니었다** — ★**로컬 DoD 가 CI 검사 5종 중 «wasm32 clippy» 한 줄을 빠뜨렸고** + 그 줄이 그 자산이 사는 **유일한 자리**다. ⇒ `CLAUDE.md` DoD 가 이제 ★**CI 명령 5줄 + toolchain 축 1줄 = «6줄»**을 + 축약 없이 싣는다(★**[게이트② 정정] 「5줄」은 계수 «1» 시절 수다 — 빠진 것은 «줄 하나»가 아니라 «두 축»이었다**). + ★그리고 ②는 «조용히» 실패하지도 않는다(`excluded package(s) not found` + 빌드 실패) ⇒ 문제는 침묵이 아니라 **늦음**이다. + ★**재개 조건은 «축 둘»이다**(정정): **축① `- run:` 줄** · ★**축② toolchain 매트릭스** — 세는 명령 둘 다 §4 에 박았다. + ★**오늘의 값 = 축① 0 · 축② 0**(착수 시 축① **5** · 축② **1**). ★세 돌연변이(beta 제거·nightly 추가·`- run:` 추가)로 + ★**각 축이 «자기 자리»에서만 반응함을 실측**했다. ★**C7 고지**: DoD 블록 «개악»과 **OS 축 3종**은 이 대조가 못 잡는다. + ⇒ ★★**[게이트② 2차 정정 · fix2] 「고쳤다고 «신고»한 것이 산출물에 남아 있었다」 — 두 건.** + ⑴「다섯 → 여섯」 통일이 ★**4자리 중 1자리**만 됐다 ⑵워크로그 `.json` `verification` 이 ★**diff 에 한 줄도 없이** + 철회된 라벨(「beta 도 ok — 깨져도 무해」)을 «검증 기록» 필드에 그대로 싣고 있었다. + ★**전문 검색으로 반려 밖에서 «더» 찾았다**: 「CI 명령 5줄」 파생 수 **6자리** · 워크로그 재개 조건 블록이 + «축① 만» 실은 것 · 워크로그 검증 표에 `+beta` 행이 없던 것. ⇒ ★**전건 동기 + 마감 절차 3줄을 §4 에 규율로 박았다.** + ⇒ ★★**[게이트② 3차 정정 · fix3] ⑴수 라벨 「beta error 7」이 «거짓»이었다 — 착지본 «18자리» 전건 정정.** + 실측(9곳 삭제 → `cargo fmt --all` → beta clippy): ★**rc=101 · `double_must_use` 진단 «6»건** + (전건 `jvm/src/jvm.rs:140·404·438·779·791·1016`) · cargo 요약 줄이 「due to **6** previous errors」라 말한다. + ★**「7」은 `grep -c '^error'`(진단 6 + 요약 줄 1)의 값**이고 그 필드가 내건 정의가 아니었다 ⇒ ★**화해 문장 철회.** + ★**「6」은 «하한»이다** — `jvm-bytecode` 가 `jvm` 에 의존해 jvm 이 깨지면 그 크레이트는 **린트되지 않는다**. + ⑵잔존 4건(워크로그 「문서 2파일」 2자리 · json `title` · 「채택 제안 둘」 3자리) + ★**PR 제목**까지 동기. + ⑶★**«5건째»를 스스로 찾았다**(`changes[0]`) — ★**필터 걸린 sweep 이 «거짓 0»을 냈기 때문**이고(zsh 단어분할), + 그 교훈을 §4 마감 절차 **4번**으로 박았다. + ★게이트③ 완료: PR #25 — ★★**`--merge` 착지**(★`--squash` 아님 · 등재 repo `contracts/upstream-sync-repos.conf:22`). + 머지커밋 sha 는 회신 `merged:` 참조. ★**게이트② 5회차 만에 approve**(초판 → fix → fix2 → fix3) — + ★★**반려 넷이 전부 «내용»이 아니라 «신고한 것 ↔ 산출물»의 어긋남이었다**(계수 · 수 라벨 · 잔존 자리). + ⇒ 그 대가로 `docs/upstream-sync-approach.md` §4 에 ★**마감 절차 4줄**이 남았다(착지본 전문 검색 · + 분모 = 파일 × 필드 · 파생 수 · ★**필터 걸린 검색의 «거짓 0»**). + ★**남은 minor 하나(F1 · 워크로그 `.md:54` 의 「CI 검사 «한 줄»」)는 이 회차가 «고치지 않았다»** — + 다음에 그 파일을 만지는 회차가 §4(:90)와 «같은 표지»를 달면 닫힌다. - [rustjava-upstream-sync-s8-rename-sweep-decision] ★★**upstream 컷 `bd42427`(개명 스윕 · 12커밋) 머지 — ★★★`merge-base` `ba5797b` → `bd42427` · behind ★**12 → 0** · 부모 2개 ⇒ ★★**upstream 을 «완전히» 따라잡았다.** 착수 재측정(신 서식): **누적 8 · 델타 +8**(base `3fb08a8` · merge-base `ba5797b`) — 「재서 8 이었다」. diff --git a/docs/upstream-sync-approach.md b/docs/upstream-sync-approach.md index 12694a2..31fea7d 100644 --- a/docs/upstream-sync-approach.md +++ b/docs/upstream-sync-approach.md @@ -185,6 +185,159 @@ upstream 은 여기서만 `UTF-8`/`EUC-KR` 하드코딩을 유지한다 ⇒ 그 --- +### ★★★[2026-09-04 결정] 「우리 자산이 낡는다」 — ★**상시 조항을 «넣지 않는다». 대신 구멍 하나를 막았다** + +★**채택 제안 ★셋**(`2026-09-04-upstream-sync-s6#p1` · `2026-09-04-upstream-sync-s7#p1` · ★`2026-09-04-upstream-sync-s8#p0`)에 대한 **결정**이다. +★**[fix3 정정] 초판은 「둘」이었다** — 세 번째 판(`s8#p0`)이 2026-09-04 13:07 에 도착해 총괄이 「이미 발권됨」으로 해제했고, `adoptedProposals` 는 **세 ref** 다. + +**결정문**: +> ★**동기 회차 계약에 「우리 자산 서술자 목록을 문서화하고 머지 후 재확인한다」 상시 조항을 «넣지 않는다».** +> ★**대신 «구멍 하나»를 막는다 — 로컬 DoD 가 CI 의 «매트릭스»를 재현하지 않았다.** +> ⇒ `CLAUDE.md` §Definition of Done 이 이제 **CI 명령 5줄 + toolchain 축 1줄 = «6줄»**을 축약 없이 싣는다. + +★**[2026-09-04 정정 — 게이트② `request-changes`] 위 결정문의 초판은 「그물이 없던 자리 «하나» — CI 검사 5종 중 +wasm32 clippy 한 줄」이었다.** 그것은 ★**계수 «1» 시절의 문면**이고, 계수가 **2**로 정정된 지금은 **낡았다**: +빠진 것은 «줄 하나»가 아니라 ★**«두 축»**(② target 축 = wasm32 `- run:` 줄 · ⑦ toolchain 축 = beta)이고, +둘은 ★**한 근인의 두 얼굴**이다. ⇒ 그래서 DoD 는 **5줄이 아니라 6줄**이다. + +★**왜 조항이 아닌가 — «세고» 정했다**(먼저 목록을 만들지 않았다). 자산 8건을 **돌연변이로 깨뜨려** 무엇이 잡는지 쟀다: + +| # | 우리 자산 | 돌연변이 | 무엇이 잡나 | +|---|---|---|---| +| ① | 픽스처 경로 문자열 | `test-data/` → `test_data/` | ★`cargo test` **RED** | +| ② | ★**CI 워크플로의 크레이트 이름**(`--exclude test-utils`) | `test-utils` → `test_utils` | ★★**로컬 4종 «어느 것도» 안 잡는다 — CI 만** | +| ③ | `System.setProperty` 서술자 | `String` → `Object` | ★`cargo test` **RED** | +| ④ | charset 라우팅(PR #5) | `Charset::resolve` → 폴백 우회 | ★`cargo test` **RED** | +| ⑤ | 수동 span(PR #4) | `.instrument(span)` 삭제 | ★`clippy` **RED**(미사용 import) | +| ⑥ | `ClassFormatError` 종류 단정(PR #3) | `ClassFormatError` → `Throwable` | ★`cargo test` **RED** | +| ⑦ | `double_must_use` allow(PR #14) | ★**9곳 «전건» 삭제** | ★★**CI 만**(stable clippy **0** ↔ ★**beta clippy RED** — rc=101 · 진단 **6**건) | +| ⑧ | 워크로그 잠금 스크립트 경로 | 스크립트 이동 | 로컬 DoD **와** CI 둘 다 잡는다(rc=2) | + +⇒ ★★**「아무것도 없음」 칸은 «2개»(② · ⑦)다.** + +★★**[2026-09-04 정정 — 게이트② `request-changes`] 초판은 ⑦을 「깨져도 무해」로 적었고 그것은 «거짓»이다.** +근인은 ★**돌연변이를 «비하중» 자리에 넣은 것**이다 — 초판은 `jvm-bytecode/src/interpreter.rs:1` **한 곳**만 지웠는데 +그 크레이트 수준 attribute 는 **같은 파일의 함수별 `#[allow]` 에 가려진 «중복»**이고, `jvm/src/jvm.rs` 에 **7곳**이 그대로 남아 있었다. +★**세 갈래 재측정**(각 회 `cargo fmt --all` 로 삭제 artifact 정규화 후 · 2026-09-04): + +| 돌연변이 | stable clippy | ★**beta clippy** | +|---|---|---| +| ⓪ 무돌연변이(기준선) | 0 | **0** | +| ① `interpreter.rs:1` 만 삭제(★초판이 한 것) | 0 | **0** ← 초판 결과 재현됨 | +| ② `jvm.rs:2` 만 삭제(함수별 6 존치) | 0 | **0** | +| ★**③ 9곳 «전건» 삭제** | 0 | ★**RED** — rc=**101** · `double_must_use` 진단 **6**건(`jvm/src/jvm.rs:140·404·438·779·791·1016`) · 요약 줄 「due to **6** previous errors」 | + +⇒ ★**「지워도 stable·beta 둘 다 통과한다」는 «중복 attribute 하나»에만 참이고 «자산»에는 «거짓»이다.** +★**⑦의 그물은 «beta clippy» 뿐이고, 그것이 로컬 DoD 에 없었으므로 «CI 만» — ② 와 «문자 그대로 같은 칸»이다.** +★★**그래서 「1개면 그 하나를 고쳐라」 규칙은 이 표에 적용되지 않는다 — 계수가 2다.** + +★★**그리고 ② 는 «조용히» 실패하지도 않았다 — 그 정정이 처방을 더 좁혔다.** +낡은 이름으로 치면 cargo 가 ★**`warning: excluded package(s) 'test_utils' not found in workspace`** 를 찍고 **빌드가 깨진다**(rc≠0). +⇒ 문제는 «침묵»이 아니라 ★**«늦음»**이다(push 후 CI 에서만 난다). +⇒ ★**근인은 「자산 목록이 없다」가 아니라 ★«로컬 DoD 가 CI 매트릭스를 재현하지 않는다»**이다. + +★★**[2026-09-04 정정 — 계수 2 위에서 논거를 다시 세운다] 결론은 같지만 «이유»가 다르다.** +초판 논거(「1개니까 그 하나를 고쳐라」)는 ★**계수 정정으로 무효**가 됐다. 새 논거는 이것이다: +- ★★**② 와 ⑦ 은 «다른 자산»이 아니라 «한 근인의 두 얼굴»이다** — ②는 **빠진 `- run:` 줄**(wasm32 **target** 축) · + ⑦은 **빠진 toolchain 축**(beta). ⇒ ★**근인 «하나»(DoD ≠ CI 매트릭스)를 고치면 «둘 다» 그물을 얻는다.** + ★**실측**: DoD 에 `cargo +beta clippy --all -- -D warnings` 를 넣자 ⑦의 9곳 전건 삭제가 + ★**로컬에서 RED**(rc=101 · 진단 **6**건)**로 잡힌다**(종전엔 CI 에서만 났다). +- ★**「조항이 «여럿»이면?」도 검토하고 기각한다** — 아래 사료 표대로 세 방향은 잡는 그물이 각각 다르므로 + 방향마다 조항을 두면 **셋**이 되고, 그중 둘(테스트·컴파일 축)은 ★**이미 기계가 지키는 자리에 사람 확인을 얹는 것**이다. +- ⇒ 계약이 준 규칙(「1개면 …」)은 **적용되지 않지만**, 그 «취지»(★**「자산 목록」이 아니라 «구멍»을 고쳐라**)는 + 계수 2 에서도 그대로 성립한다. ⇒ ★**구멍 «하나»(매트릭스 재현)를 고쳤다 — 축 두 개(target · toolchain)로.** + +★**사료 — 「세 번」·「두 방향」은 확정하되, ★«한 조항으로 못 덮는다»가 결론이다**: + +| 회차 | 낡은 것 | 방향 | 잡은 것 | +|---|---|---|---| +| S3 | `test_class_format.rs` 문구 단정 3건 | ⑴upstream **신규/판본 교체** | 테스트 | +| S5 | io 테스트 **5곳** `setProperty` 서술자 | ⑴ | 테스트 | +| S6 | regex 테스트 **3곳** 〃 | ⑴ | 정독(테스트 «전») | +| S7 | 우리 고유 테스트 **5곳** `invoke_virtual` | ⑵**공용 API 시그니처 변경** | 컴파일 | +| S8 | `rust.yml` 크레이트명 · `test_class_format.rs` 경로 4곳 | ⑶**개명** | ★CI 만 / 테스트 | + +⇒ ★★**셋은 «같은 형태»가 아니다** — 잡는 그물이 각각 다르다(테스트·컴파일·CI). +★**그래서 조항 하나로 덮으려 하면 «이미 그물이 있는 여섯 자리»에까지 사람 확인을 얹게 된다** — 그것이 비용이다. + +★★**[2026-09-04 정정 — C5] 「다섯」은 오기였다. 판정 시점 기준으로 «여섯»이 맞다.** 세는 법을 병기한다: +> 위 돌연변이 표에서 **★그물 칸이 「CI 만」이 «아닌» 행의 수**. +> **판정 시점(수리 전)**: 8행 − 「CI 만」 **2행**(② · ⑦) = ★**6** · **수리 후**: ★**8**(둘 다 로컬 그물을 얻었다). +```sh +# 표의 그물 칸을 센다(문서 실측 · 「CI 만」이 아닌 행) +sed -n '/^| # | 우리 자산/,/^$/p' docs/upstream-sync-approach.md \ +| /usr/bin/grep -c '^| [①-⑧]' ; \ +sed -n '/^| # | 우리 자산/,/^$/p' docs/upstream-sync-approach.md \ +| /usr/bin/grep -c 'CI 만' +``` + +### ★★재개 조건 — «세는 법»과 «오늘의 값» (★**축이 «둘»이다**) + +> ★**CI 가 치는 것 중 로컬 DoD 에 없는 것이 «1건이라도» 생기면 이 결정을 다시 연다.** +> ★★**«치는 것»은 «줄»만이 아니라 «매트릭스 차원»도 포함한다** — 그래서 축이 둘이다. + +```sh +cd "$(git rev-parse --show-toplevel)" +DOD=$(sed -n '/## Definition of Done/,/^- 착수·완료마다/p' CLAUDE.md) + +# ★축① — CI 의 `- run:` 줄 ∖ 로컬 DoD (0 이어야 한다) +LC_ALL=C /usr/bin/grep -E '^[[:space:]]+- run: ' .github/workflows/rust.yml | sed 's/^[[:space:]]*- run: //' \ +| while IFS= read -r c; do printf '%s' "$DOD" | LC_ALL=C /usr/bin/grep -qF -- "$c" || echo "MISSING-RUN: $c"; done \ +| /usr/bin/grep -c . + +# ★축② — CI 의 toolchain 매트릭스 ∖ 로컬 DoD (0 이어야 한다) +LC_ALL=C /usr/bin/grep -E '^[[:space:]]+rust: \[' .github/workflows/rust.yml \ +| sed 's/.*\[//; s/\].*//; s/, */\n/g' \ +| while IFS= read -r tc; do + [ "$tc" = stable ] && { printf '%s' "$DOD" | LC_ALL=C /usr/bin/grep -qE 'cargo (fmt|clippy|test)' || echo "MISSING-TC: $tc"; continue; } + printf '%s' "$DOD" | LC_ALL=C /usr/bin/grep -qF -- "cargo +$tc " || echo "MISSING-TC: $tc" + done | /usr/bin/grep -c . +``` + +★**오늘의 값**(2026-09-04 정정 후): **축① `0`** · **축② `0`**. +※착수 시엔 **축① 5 · 축② 1**(beta 가 DoD 에 없었다) — 축①은 원 회차가, 축②는 정정 회차가 0 으로 만들었다. + +★★**축②를 왜 따로 두는가 — 축① 만으로는 «구조적으로» 못 본다.** 실측(정정 회차): + +| 워크플로 돌연변이 | 축① | 축② | ★옳은 값인가 | +|---|---|---|---| +| ⓪ 무돌연변이 | **0** | **0** | ✔ | +| ① `rust: [stable, beta]` → `[stable]`(CI 가 beta 를 **버림**) | 0 | **0** | ✔ ★**0 이 «옳다»** — 술어가 `CI ∖ DoD` 라 **DoD 가 더 엄격한 것은 «구멍»이 아니다**(로컬이 CI 보다 한 축 더 돌 뿐) | +| ② → `[stable, beta, nightly]`(CI 가 **늘림**) | 0 | ★**1** | ✔ **축②만** 반응 | +| ③ `- run: cargo doc --no-deps` 1줄 추가 | ★**1** | 0 | ✔ **축①만** 반응 | + +⇒ ★**①이 0 인 것을 «검사가 안 듣는다»로 읽지 마라** — 그 방향은 애초에 재개 사유가 아니다. +★**②·③ 이 각각 «자기 축»에서만 1 을 내는 것**이 두 축이 «겹치지 않고 덮는다»는 증거다. + +★**1 이상이 되면**: ⑴그 줄/축을 DoD 에 넣거나 ⑵넣을 수 없는 이유를 적고 **이 결정을 재검토**하라. +★**「조항이 필요하다」로 바로 가지 마라 — 그때도 «먼저 세라»**(위 돌연변이 표를 다시 만들면 된다). + +★★**[C7 고지 — 이 처방이 «못 잡는» 것]** +⒜**DoD 블록 자체의 개악은 자동으로 안 잡힌다** — 누가 그 블록에서 한 줄을 지우면 축①이 **1** 이 되지만, +★**그 축을 «돌리는 것»이 사람이다.** 기계 강제(DoD ↔ `rust.yml` 대조 CI job)는 이 회차 범위 밖이고, +★**고칠 수 없으면 «고지»가 처방이다.** ⇒ ★**이 절을 읽는 회차는 「축①·축②를 «실제로 돌렸는가»」를 회신에 적어라.** +⒝★**OS 축(macos·ubuntu·windows 3종)은 로컬에서 재현할 수 없다** — 그 차원은 ★**CI 가 «유일한» 그물**이고 +대조 대상이 아니다. **결함이 아니라 «알고 두는» 값이다.** + +★★★**[2026-09-04 규율 — 게이트② 2회 연속 반려가 만든 것] 「고쳤다」를 쓰기 «전»에 «세라».** +이 리니지는 ★**「해소했다고 신고한 것이 산출물에 그대로 남아 있는」 형태로 두 번 반려됐다.** 실측: +「다섯 → 여섯 통일」은 ★**4자리 중 1자리**만 됐고, 워크로그 `.json` 의 `verification` 은 ★**diff 에 한 줄도 없이** +철회된 라벨(「beta 도 ok — 깨져도 무해」)을 «검증 기록»이라는 더 권위 있는 필드에 계속 싣고 있었다. +⇒ ★**정정 회차의 마감 절차 3줄**(이 절을 읽는 회차는 그대로 하라): +1. ★**`git diff` 로 확인하지 마라 — «착지본 전문 검색»으로 확인하라.** diff 는 «안 바뀐 자리»를 보여주지 않는다. + `LC_ALL=C /usr/bin/grep -rn '<옛 문자열>' --include='*.md' --include='*.json' .` → ★**0건이어야 한다.** +2. ★**분모는 「이 PR 이 만지는 문서 «전부» × 그 안의 필드 «전부»」다**(`git diff --name-only ..HEAD`). + ★**`.json` 의 «필드»가 사각이다** — `summary`·`changes`·`issues` 를 고치고 `verification` 을 빠뜨리기 쉽다. +3. ★**정정한 수는 «파생 수»를 데리고 다닌다.** 계수를 1 → 2 로 고치면 「다섯」·「5줄」·「자리 하나」가 전부 낡는다 — + ★**고친 수 하나마다 그 수에서 유도된 문장을 «검색해서» 세라.** +4. ★★**[2026-09-04 추가 · fix3] 「0건」을 «필터 걸린 검색»으로 받지 마라 — 이 회차가 «거짓 0»을 실제로 받았다.** + fix3 이 잔존을 `grep … | grep -v '초판\|정정\|철회'` 로 세어 **전 패턴 0** 을 얻었는데, 그것은 ⑴zsh 가 따옴표 없는 + `$F`(파일 목록 변수)를 ★**단어분할하지 않아** grep 이 «파일 하나도 못 열고» stderr 로 죽었고 ⑵파이프의 `-c` 가 + 그 빈 출력을 **0** 으로 센 결과였다. ★**진짜 잔존이 그 뒤에 «하나 더»(`changes[0]`) 있었다.** + ⇒ ★**세는 법**: 파일을 **인자로 나열**하고(변수 전개에 기대지 마라 · `set -- $VAR` 도 zsh 에선 1개다), + ★**먼저 «필터 없이» 총계를 내고 «그다음» 한 건씩 «정정 기록인가 잔존인가»를 눈으로 갈라라.** + ★**필터는 세는 단계가 아니라 «읽는» 단계에 붙인다** — 세는 단계에 붙이면 그 필터가 잔존을 함께 지운다. + ## 5. 단계 분할 — ★**커밋 수로 자르지 마라. 충돌은 앞쪽 7커밋에 몰려 있다** 각 컷 지점에서 `git merge-tree --write-tree --name-only origin/main ` 을 돌린 실측: diff --git a/docs/worklog/2026-09-04-sync-contract-stale-assets-decision.json b/docs/worklog/2026-09-04-sync-contract-stale-assets-decision.json new file mode 100644 index 0000000..9b22273 --- /dev/null +++ b/docs/worklog/2026-09-04-sync-contract-stale-assets-decision.json @@ -0,0 +1,52 @@ +{ + "schema": 1, + "date": "2026-09-04", + "title": "「우리 자산이 낡는다」 상시 조항 판정 — 넣지 않고 로컬 DoD 가 CI 매트릭스를 재현하지 않던 «두 축»을 막았다", + "services": [ + "RustJava" + ], + "taskId": "rustjava-sync-contract-standing-clause-for-our-assets-going-stale", + "summary": "운영자 채택 제안 «셋»(2026-09-04-upstream-sync-s6#p1 · …-s7#p1 · ★…-s8#p0)에 대한 결정이다. ★[fix3 정정] 초판은 「둘」이라 적었다 — 같은 제안의 세 번째 판(s8#p0)이 2026-09-04 13:07 에 도착해 총괄이 「이미 발권됨」으로 해제했고, adoptedProposals 는 세 ref 다. 결론: 동기 회차 계약에 「우리 자산 서술자 목록을 문서화하고 머지 후 재확인한다」 상시 조항을 넣지 않는다. 대신 구멍 하나(로컬 DoD 가 CI 매트릭스를 재현하지 않던 것)를 막았다 — CLAUDE.md §Definition of Done 이 이제 CI 명령 5줄 + toolchain 축 1줄 = «6줄»을 축약 없이 싣는다. 목록 문서화로 시작하지 않고 먼저 셌다: 우리 자산 8건을 돌연변이로 깨뜨려 무엇이 잡는지 실측했고(경로 문자열·setProperty 서술자·charset 라우팅·ClassFormatError 종류 단정은 cargo test RED · 수동 span 은 clippy RED · double_must_use allow 는 stable·beta 둘 다 ok 라 깨져도 무해 · 워크로그 스크립트는 로컬 DoD 와 CI 둘 다), ★[게이트② request-changes 정정] 「아무것도 없음」 칸은 1개가 아니라 «2개»다 — ② CI 워크플로의 --exclude 크레이트 이름 · ⑦ double_must_use allow. 초판이 ⑦을 「깨져도 무해」로 적은 것은 돌연변이를 «비하중» 자리(중복 crate-level attribute 한 곳)에 넣었기 때문이고, 9곳 전건 삭제로 재측정하면 stable 0 · beta RED(rc=101 · double_must_use 진단 6건 — 전건 jvm/src/jvm.rs:140·404·438·779·791·1016) 이다 ⇒ ⑦의 그물도 «CI 만»이다. 계수가 2 라 「1개면 그 하나를 고쳐라」 규칙은 적용되지 않는다. 그리고 그 1개도 조용히 실패하지 않는다 — cargo 가 warning: excluded package(s) not found 를 찍고 빌드가 깨진다 ⇒ 문제는 침묵이 아니라 늦음이고, 근인은 「자산 목록이 없다」가 아니라 「로컬 DoD 가 CI 검사 5종 중 wasm32 clippy 한 줄을 빠뜨렸다」였다. 그 줄이 --exclude 크레이트 이름이 사는 유일한 자리다. ★결론은 그대로 「조항을 넣지 않는다」이나 논거를 다시 세웠다: ②와 ⑦은 다른 자산이 아니라 «한 근인의 두 얼굴»이다 — ②는 빠진 - run: 줄(target 축) · ⑦은 빠진 toolchain 축(beta) ⇒ 근인 하나(로컬 DoD 가 CI 매트릭스를 재현하지 않는다)를 고치면 둘 다 그물을 얻는다. DoD 에 cargo +beta clippy 를 넣자 ⑦의 9곳 전건 삭제가 로컬에서 RED(rc=101 · 진단 6건)로 잡힌다. 재개 조건도 축을 둘로 늘렸다(줄 문자열 축 + 매트릭스 차원 축) — 종전 명령은 rust: [stable, beta] 를 [stable] 로 줄여도 [stable, beta, nightly] 로 늘려도 0 을 냈다. ★[게이트② fix2 정정] 초판 문면의 「그물이 없던 자리 하나 = CI 명령 5줄」은 계수 «1» 시절의 파생 수다 — 계수가 2 가 되며 빠진 것은 «줄 하나»가 아니라 «두 축»(② target · ⑦ toolchain)이고, 따라서 DoD 는 5줄이 아니라 «6줄»이다. 그 파생 수가 REPORT.md · STATE.md · 워크로그 .md/.json · approach.md 에 남아 있던 것을 fix2 에서 전수 동기했다.", + "changes": [ + "CLAUDE.md §Definition of Done — CI 가 치는 명령을 5줄 → ★6줄로(cargo +beta clippy --all -- -D warnings 추가). CI 는 이 검사를 6셀(toolchain 2 × OS 3)로 치는데 repo 에 rust-toolchain.toml 이 없어 맨 cargo 는 stable 1개로만 돈다 — toolchain 축을 안 적으면 「rust.yml 이 치는 그대로」가 거짓이 된다. 실측 근거(9곳 전건 삭제 시 stable 0 · beta RED — rc=101 · double_must_use 진단 6건)와 OS 축은 로컬 재현 불가라 CI 가 유일한 그물이라는 고지를 함께 넣었다.", + "docs/upstream-sync-approach.md §4 결정 절 — ⑦행 라벨을 「깨져도 무해」에서 ★「CI 만」으로 정정(세 갈래 재측정 표 첨부) · 계수 1 → ★2 · 결정 논거 재구성(둘은 한 근인의 두 얼굴 · 「조항이 여럿이면?」 기각) · 재개 조건에 ★축② 「매트릭스 차원 대조」 추가 · 비용 논거의 수를 「다섯」 → ★「여섯」으로 정정하고 세는 명령 병기(★fix 회차는 이 파일 «한 자리»만 고쳤고 나머지 3자리는 fix2 에서 동기했다).", + "STATE.md · REPORT.md · docs/worklog/2026-09-04-sync-contract-stale-assets-decision.{md,json} 갱신·신설.", + "★.github/workflows/rust.yml 은 무접촉 — 크레이트 이름 1건은 S8 회차가 이미 고쳐 착지시켰다.", + "★[fix2] 「신고 ↔ 산출물」 전수 동기: ⒜「다섯 자리」 잔존 3자리(REPORT.md · 워크로그 .md · .json issues[2])를 «여섯»으로 통일하고 세는 법 병기 ⒝워크로그 .json verification 을 «지금의 사실»로 갱신(⑦ 라벨 정정 표지 · 근인 대조를 축 둘로 · 축② 착수값 1 · cargo +beta clippy 실행 기록) ⒞★전문 검색이 «반려 밖에서» 더 찾은 것 — 「CI 명령 5줄」 파생 수 6자리(REPORT×3 · STATE×1 · 워크로그 .md×3 · approach.md×1 · json summary) · 워크로그 .md 재개 조건 코드블록이 «축① 만» 싣고 있던 것 · 워크로그 .md 검증 표에 cargo +beta clippy 행이 없던 것.", + "★[fix3] ⒜「beta error 7」 수 라벨을 착지본 «18자리 전건»에서 「rc=101 · double_must_use 진단 6건(전건 jvm/src/jvm.rs)」으로 정정 — CLAUDE.md 1 · approach.md 3 · REPORT.md 3 · STATE.md 2 · 워크로그 .md 3 · json 6. ★그 수는 CLAUDE.md·approach.md 처럼 «모든 동기 회차가 읽는» 파일에 박혀 나가므로 다음 회차가 근거로 인용한다. ⒝verification 의 화해 문장(「101 은 lint 건 · 7 은 크레이트별 error 수」)을 철회 — 둘 다 반증됐다. ⒞잔존 4건 동기: 워크로그 .md 의 「문서 2파일」 2자리(:5 · :163) → 6파일(규범 2 + 기록 4) · json title 을 계수 2 문면으로 · 「채택 제안 둘/2건」 3자리를 «셋»으로 맞추고 s8#p0 를 열거. ⒟PR 제목도 계수 2 문면으로 고쳤다(착지 subject 에 남는다). ★«5건째»는 이 항목 자신이었다 — changes[0] 의 「beta error 7」. 찾은 경위가 중요하다: 필터 붙인 sweep 이 «0»을 냈는데 그것은 «거짓 0»이었다(zsh 가 따옴표 없는 $F 를 단어분할하지 않아 grep 이 파일 하나도 못 열고 stderr 로 죽었고 파이프의 -c 가 0 을 셌다). ★목록으로 다시 세니 나왔다." + ], + "deploy": { + "sha": "", + "urls": [] + }, + "verification": "★[fix2 갱신 — 이 필드는 fix 회차에서 «통째로 미수정»이라 철회된 라벨을 싣고 있었다. 아래가 «지금의 사실»이다.]\n◆원 회차(2026-09-04 초판) 착수 실측: origin/main a76b305 · behind 0 · 열린 PR 0. 계약1⒝ 돌연변이(각각 깨뜨린 뒤 build/clippy/test 를 돌리고 원상 복구): ① tests/test_class_format.rs 의 test-data/ → test_data/ ⇒ build ok · clippy ok · test RED. ③ system.rs 의 setProperty 서술자 String → Object ⇒ test RED. ④ string.rs 의 Charset::resolve → from_name().unwrap() 우회 ⇒ test RED. ⑤ thread.rs 의 .instrument(span) 삭제 ⇒ clippy RED(미사용 import). ⑥ test_class_format.rs 의 ClassFormatError → Throwable ⇒ test RED. ② cargo clippy --workspace --exclude test_utils --target wasm32-unknown-unknown ⇒ rc=101 이고 문면은 warning: excluded package(s) `test_utils` not found in workspace + tokio wasm 컴파일 실패(조용한 통과가 아니다). ⑧ scripts/check-worklog-json.py 이동 ⇒ rc=2(로컬 DoD 와 CI 둘 다 그 경로를 친다). ⑦ jvm-bytecode/src/interpreter.rs 의 #[allow(clippy::double_must_use)] 삭제 ⇒ stable clippy ok · beta clippy 도 ok — ★★[게이트② request-changes 정정] 그 문장은 «거짓»이다. 돌연변이가 «비하중» 자리(같은 파일의 함수별 #[allow] 에 가려진 중복 crate-level attribute 한 곳)에 들어갔고, jvm/src/jvm.rs 의 7곳이 그대로 남아 있었다. 세 갈래 재측정(각 회 cargo fmt --all 로 삭제 artifact 정규화): interpreter.rs:1 만 삭제 ⇒ stable 0 / beta 0(초판 결과 재현) · jvm.rs:2 만 삭제 ⇒ 0 / 0 · ★9곳 «전건» 삭제 ⇒ stable 0 / ★beta RED — rc=101 · double_must_use 진단 «6»건(전건 jvm/src/jvm.rs:140·404·438·779·791·1016) · cargo 요약 줄은 「could not compile `jvm` (lib) due to «6» previous errors」. ⇒ ⑦의 그물은 «CI 만»이고 ② 와 같은 칸이다 ⇒ 계수 = 2. ★allow 9곳을 지우지 마라(지우면 beta 3셀 red). ★★[fix3 철회 — 게이트② C1] 앞선 회차가 여기 적은 화해 문장(「101 은 lint 발생 «건» · 7 은 cargo 요약의 크레이트별 error 수」)을 «철회한다». ★두 문장 다 반증됐다: 101 은 «종료코드(rc)»이고, cargo 요약 줄은 「due to «6» previous errors」라고 말한다. ★「7」이 나오는 자는 /usr/bin/grep -c '^error' «하나뿐»이다 — 진단 6줄 + 요약 줄 1 = 7 이고, 그것은 이 필드가 스스로 내건 정의가 아니었다. ★«7 이 맞다»고 재주장하지 않는다. 옳은 라벨은 「rc=101 · 진단 6건」이다. ※검수 회신의 「8곳」과 내 실측 「9곳」의 차는 interpreter.rs:1067 을 세느냐이고, 판정(stable 0 ↔ beta RED)은 동일하다. ★그리고 「6」은 «하한»이다 — jvm-bytecode 는 jvm 에 의존하므로(jvm-bytecode/Cargo.toml `jvm = { workspace = true }`) jvm 이 컴파일에 실패하면 그 크레이트는 아예 린트되지 않는다. interpreter.rs 의 진단 0 은 «깨끗해서»가 아니라 «도달하지 못해서»다.\n◆근인 대조[fix 갱신 — 초판은 «단일 축» 문면이었다]: 로컬 DoD 가 CI 의 «매트릭스»를 재현하지 않았다. 축①(target) = rust.yml 의 - run: 줄 5개 ↔ 초판 DoD 4개 ⇒ 차집합이 wasm32 clippy 한 줄. 축②(toolchain) = rust.yml 의 strategy.matrix rust: [stable, beta] ↔ 초판 DoD 에 beta 없음(repo 에 rust-toolchain.toml 이 없어 맨 cargo 는 stable 1개) ⇒ 차집합이 beta 축. 둘은 «한 근인의 두 얼굴»이다.\n◆재개 조건 세는 명령(회신 · approach.md §4 · 워크로그 .md §5 동일)의 값: 착수 시 ★축① 5 · ★축② 1 → 착지본 축① 0 · 축② 0. 축② 신설의 근거(3돌연변이 실측): rust: [stable, beta] → [stable] ⇒ 축① 0 · 축② 0(술어가 CI ∖ DoD 라 DoD 가 더 엄격한 것은 구멍이 아니다 — 옳은 값) · → [stable, beta, nightly] ⇒ 축② 1(축②만 반응) · - run: cargo doc --no-deps 1줄 추가 ⇒ 축① 1(축①만 반응).\n◆계약6[fix2 재실행 — 정정 후 DoD «6줄»을 문면 그대로]: cargo fmt --all -- --check rc=0 · cargo clippy --all -- -D warnings rc=0 · ★cargo +beta clippy --all -- -D warnings rc=0(무돌연변이 기준선) · cargo clippy --workspace --exclude test-utils --target wasm32-unknown-unknown -- -D warnings rc=0 · cargo test --all rc=0 (554 passed / 0 failed / 1 ignored · baseline 554/0/1 동수 · 새 red 0) · python3 scripts/check-worklog-json.py rc=0. 돌연변이 전부 복구 확인 · .rs 변경 0 · allow 9곳 무접촉. 계약5: .github/workflows/rust.yml:55 가 이미 --exclude test-utils 이므로 이 회차는 무접촉.", + "issues": [ + "★★[게이트② request-changes 정정] 초판의 계수 「1」은 틀렸다 — 「아무것도 없음」은 «2개»(② · ⑦)다. 근인은 돌연변이를 비하중 자리에 넣은 것이고(중복 crate-level attribute 한 곳만 삭제), 9곳 전건 삭제로 재측정하면 beta RED(rc=101 · double_must_use 진단 6건 — 전건 jvm/src/jvm.rs:140·404·438·779·791·1016) 이다. ★그리고 그 차를 놓친 «같은 근인»이 하나 더 있었다: 브리프가 「제안 2 ↔ 검수 1」의 차를 해소하라고 요구했는데 초판이 그것을 안 짚었다 — 이제 답이 나왔다: ★«2»가 맞다.", + "★이 결정이 «막는» 것은 「우리 자산이 낡는 것」 자체가 아니라 「그것이 로컬에서 안 드러나고 CI 에서만 드러나는 것」이다. 낡음 자체는 여전히 일어나고, 다만 여섯 자리는 기계가(테스트·컴파일·clippy) 이미 잡는다.", + "★잡는 그물이 축마다 다르다(테스트/컴파일/CI)는 것이 「조항 하나로 못 덮는다」의 근거다 — 조항으로 덮으면 이미 그물이 있는 «여섯» 자리에까지 사람 확인을 얹게 된다. ★세는 법: 돌연변이 표 8행 − 그물이 「CI 만」인 2행(② · ⑦) = 6(수리 후에는 8). ★[게이트② fix2 정정] 초판은 여기서 「다섯」이라 적었다 — 계수 «1» 시절의 파생 수이고, 같은 파일 issues[1] 의 「여섯」과 «자기모순»이었다. 이제 두 자리가 같은 수다.", + "★S8 이 고친 CI 크레이트 이름 1건과 이 회차의 산출은 «다른 층»이다: 전자는 증상 1건, 후자는 그 증상이 늦게 드러난 경로. 티켓이 「고쳤다고 조항 결정을 건너뛰지 마라」고 못박은 자리다.", + "머지하지 않았다 — 게이트②·③은 별 세션이다. ★게이트③은 등재 repo 라 merge_strategy: merge 필수(bin/queue-lint 검사22). upstream 동기 회차를 새로 열지 않았다(behind 0 · S9 없음).", + "★★[C1 승계 철회] 초판 후속 추천 3번(「double_must_use allow 가 이제 지워도 통과한다 — 정리할지는 별건」)을 «철회한다». 그 문장은 비하중 돌연변이에서 나온 것이고, 9곳을 전건 지우면 beta clippy 가 RED 다(rc=101 · double_must_use 진단 6건). ★그대로 두면 다음 회차가 그 문장을 근거로 allow 를 지우고 beta 3셀을 빨갛게 만든다 ⇒ ★allow 9곳을 지우지 마라.", + "★[C7 고지] ⑸ⓐ 처방(DoD 가 CI 명령을 verbatim 싣는 것)의 «개악»을 자동으로 잡는 것은 없다 — 누군가 DoD 블록에서 한 줄을 지우면 재개 조건 축①이 1 이 되지만, 그 축을 «돌리는 것»이 사람이다. ★기계 강제(예: DoD 블록과 rust.yml 을 대조하는 CI job)는 이 회차 범위 밖이고, 고칠 수 없으면 «고지»가 처방이다 ⇒ 여기 적는다.", + "★[C7 고지 2] OS 축(macos·ubuntu·windows 3종)은 로컬에서 재현할 수 없다 — 그 차원은 CI 가 유일한 그물이고 재개 조건 대조의 대상이 아니다. 이것은 결함이 아니라 «알고 두는» 값이다.", + "★[게이트③ 집행] PR #25 를 --merge 로 착지시켰다(등재 repo — 스쿼시는 계보를 접는다). 동봉 «전» head 가 검수 핀 a7d0f2a986b9fda36d4c1d1f1564aa5916a2b109 과 일치함을 확인했고(git fetch 를 먼저 쳐 거짓 일치를 막았다), 이 원장 동봉으로 head 가 움직인 전/후 sha 는 머지 회신에 적었다. 배포 워크플로 0개 ⇒ 배포 0. 코드(.rs) 변경 0." + ], + "proposals": [ + { + "title": "upstream 동기를 «정기 축»으로 둘지 판정하라 — behind 0 인 지금이 적기이고 아직 열려 있다", + "plainSummary": "원본 저장소를 완전히 따라잡았지만 다음에 언제 다시 받아올지가 정해져 있지 않다. 아무도 챙기지 않으면 격차가 다시 벌어지고, 벌어진 뒤에 받으면 훨씬 비싸다.", + "userBenefit": "격차가 작을 때 받아오면 충돌이 작고 우리 고유 기능이 원본 변경에 삼켜질 위험도 작다. 이번 캠페인이 behind 33 에서 시작해 여덟 회차가 걸린 것이 그 반대 사례다.", + "why": "S8 착지로 behind 가 0 이 됐고 계획서 §5 의 7회차 + S8 이 모두 끝났다. 계획서는 「어느 컷을 언제」를 정했지만 「따라잡은 뒤」를 정하지 않았다. 이 캠페인 비용의 대부분이 「격차가 커진 뒤 한꺼번에 받은 것」에서 나왔다 — S5 는 설계 판단 1건, S8 은 개명 스윕 판정이 필요했고 둘 다 격차가 작았으면 훨씬 쌌다. 이 제안은 S8 워크로그 proposals[1] 과 같은 것이며 아직 미처분이다.", + "tradeoff": "⑴정기 축(예: 월 1회 또는 behind 임계)으로 두면 회차가 작아지지만 발권·검수 비용이 상시로 든다. ⑵필요할 때만 받으면 비용이 0 이지만 격차가 다시 33 이 될 수 있고 그때는 이번처럼 여덟 회차가 든다. ⑶behind 를 재는 것만 자동화하고 발권은 사람이 하면 중간이지만 그 기구가 지금 없다.", + "effort": "XS — 판정. 정기 축을 고르면 그 축의 발권 방식은 별건.", + "target": "docs/upstream-sync-approach.md · 총괄 발권 정책" + } + ], + "resolvedIssues": [], + "adoptedProposals": [ + "2026-09-04-upstream-sync-s6#p1", + "2026-09-04-upstream-sync-s7#p1", + "2026-09-04-upstream-sync-s8#p0" + ], + "declinedProposals": [] +} diff --git a/docs/worklog/2026-09-04-sync-contract-stale-assets-decision.md b/docs/worklog/2026-09-04-sync-contract-stale-assets-decision.md new file mode 100644 index 0000000..4ad992e --- /dev/null +++ b/docs/worklog/2026-09-04-sync-contract-stale-assets-decision.md @@ -0,0 +1,173 @@ +# [2026-09-04] 「우리 자산이 낡는다」 상시 조항 «판정» — 넣지 않고 «구멍 하나»를 막았다 + +**티켓**: `rustjava-sync-contract-standing-clause-for-our-assets-going-stale` +**채택 제안 ★3건**: `2026-09-04-upstream-sync-s6#p1` · `2026-09-04-upstream-sync-s7#p1` · ★`2026-09-04-upstream-sync-s8#p0` +**성격**: 결정 회차 · 코드(`.rs`) 변경 **0** · 문서 ★**6파일**(규범 2: `CLAUDE.md`·`docs/upstream-sync-approach.md` / 기록 4: `REPORT.md`·`STATE.md`·이 `.md`·`.json`) +★**[fix3 정정] 초판은 「제안 2건 · 문서 2파일」이었다** — 제안은 세 번째 판(`s8#p0`)이 도착해 **셋**이고, +「문서 2파일」은 **규범 문서만** 센 수라 이 PR 이 실제로 만지는 **6파일**과 어긋났다. + +--- + +## 1. ★먼저 «셌다» — 목록 문서화로 시작하지 않았다 + +티켓이 못박은 순서 그대로: ★**「그물이 없는 자리」의 «수»가 이 결정을 정한다.** +자산 8건을 ★**돌연변이로 깨뜨려**(추론 0) 무엇이 잡는지 쟀다. + +| # | 우리 자산(출처) | 돌연변이 | build | clippy | test | ★그물 | +|---|---|---|---|---|---|---| +| ① | 픽스처 경로 문자열(PR #3) | `test-data/` → `test_data/` | ok | ok | ★**RED** | 있음 | +| ② | ★**CI 워크플로 `--exclude test-utils`**(S8) | `test-utils` → `test_utils` | — | — | — | ★★**없음(CI 만)** | +| ③ | `System.setProperty` 서술자(PR #5) | `String` → `Object` | ok | ok | ★**RED** | 있음 | +| ④ | charset 라우팅(PR #5) | `Charset::resolve` → 폴백 우회 | ok | ok | ★**RED** | 있음 | +| ⑤ | 수동 span(PR #4) | `.instrument(span)` 삭제 | ok | ★**RED** | ok | 있음 | +| ⑥ | `ClassFormatError` 종류 단정(PR #3) | `ClassFormatError` → `Throwable` | ok | ok | ★**RED** | 있음 | +| ⑦ | `double_must_use` allow(PR #14) | ★**9곳 «전건» 삭제** | ok | ★**beta RED**(rc=101 · 진단 **6**건) | ok | ★★**없음 — CI 만**(② 와 같은 칸) | +| ⑧ | 워크로그 잠금 스크립트 경로(PR #15) | 스크립트 이동 | — | — | — | 있음(로컬 DoD **와** CI 둘 다 · rc=2) | + +★★**[2026-09-04 정정 — 게이트② `request-changes`] 초판의 ⑦행 「깨져도 무해」는 «거짓»이다.** +근인은 ★**돌연변이를 «비하중» 자리에 넣은 것**이다 — 초판은 `interpreter.rs:1` **한 곳**만 지웠는데 +그것은 **같은 파일 함수별 `#[allow]` 에 가려진 «중복»**이고 `jvm/src/jvm.rs` 에 **7곳**이 그대로 남아 있었다. +★**세 갈래 재측정**(각 회 `cargo fmt --all` 정규화 후): + +| 돌연변이 | stable | ★beta | +|---|---|---| +| ⓪ 무돌연변이 | 0 | **0** | +| ① `interpreter.rs:1` 만(★초판) | 0 | **0** ← 초판 결과 재현됨 | +| ② `jvm.rs:2` 만(함수별 6 존치) | 0 | **0** | +| ★③ **9곳 전건** | 0 | ★**RED** — rc=**101** · `double_must_use` 진단 **6**건(전건 `jvm/src/jvm.rs`) | + +⇒ ★**⑦의 그물은 «beta clippy» 뿐이고 그것이 로컬 DoD 에 없었으므로 «CI 만» — ② 와 같은 칸이다.** + +⇒ ★★**「아무것도 없음」 칸은 «2개»(② · ⑦)다.** + +## 2. ★★그 1개도 «조용히» 실패하지 않는다 — 그 정정이 처방을 더 좁혔다 + +낡은 이름으로 치면 cargo 가 이렇게 말한다: + +``` +warning: excluded package(s) `test_utils` not found in workspace `…/RustJava` +error: Only features sync,macros,io-util,rt,time are supported on wasm. +error: could not compile `tokio` (lib) due to 1 previous error ← rc≠0 +``` + +⇒ ★**문제는 «침묵»이 아니라 «늦음»이다** — push 후 CI 에서만 난다. +⇒ ★★**근인은 「자산 목록이 없다」가 «아니라» — 로컬 DoD 가 CI 검사 한 줄을 빠뜨린 것이다.** + +**대조 실측**: + +| | 검사 | +|---|---| +| CI(`rust.yml`) | `fmt --all -- --check` · `clippy --all` · ★**`clippy --workspace --exclude test-utils --target wasm32`** · `test --all` · `check-worklog-json.py` = **5** | +| 로컬 DoD(종전) | `cargo fmt --check` · `cargo clippy` · `cargo test` · `check-worklog-json.py` = **4** | + +★**빠진 하나가 정확히 wasm32 줄**이고, ★**그 줄이 `--exclude <크레이트 이름>` 이 사는 «유일한» 자리**다. +⇒ S8 의 개명(`test_utils`→`test-utils`)이 **로컬 어디에서도 안 드러난** 이유가 이것이다. + +## 3. 사료 확정 — 「세 번」·「두 방향」 (계약 2) + +| 회차 | 낡은 것 | ⒝ 방향 | 잡은 것 | +|---|---|---|---| +| **S3** | `test_class_format.rs` 문구 단정 3건 | ⑴upstream **신규/판본 교체** | 테스트 | +| **S5** | io 테스트 **5곳** `setProperty` 서술자 | ⑴ | 테스트 | +| **S6** | regex 테스트 **3곳** 〃 | ⑴ | ★정독(테스트 «전») | +| **S7** | 우리 고유 테스트 **5곳** `invoke_virtual` | ⑵**공용 API 시그니처 변경** | ★컴파일 | +| **S8** | `rust.yml` 크레이트명 · `test_class_format.rs` 경로 4곳 | ⑶**개명** | ★CI 만 / 테스트 | + +⒜ **「세 번」 = S3·S5·S6**(제안 `#p1` 이 센 것) · ⒝ **「두 방향」 = ⑴·⑵**(S7 이 가른 것) · +★**S8 이 ⑶을 더해 «셋»이 됐다.** + +⒞ ★★**같은 형태인가 → «아니다».** 잡는 그물이 각각 다르다(테스트 · 컴파일 · CI). +⇒ ★**조항 하나로 못 덮는다** — 덮으려 하면 **이미 그물이 있는 여섯 자리에까지 사람 확인을 얹는다**. 그것이 비용이다. +★**세는 법**(수리 전 기준): §1 돌연변이 표 **8행** − 그물이 「CI 만」인 **2행**(② · ⑦) = ★**6**. +★**[게이트② 정정] 초판의 「다섯」은 계수 «1» 시절의 파생 수다** — 계수가 **2**로 정정되며 **6**이 맞다. +(`:93` 의 「여섯 자리는 이미 기계가 지킨다」와 이제 **같은 수**다.) + +## 4. ★★결정문 · 적은 «자리» · 이유 (계약 3) + +> ★**동기 회차 계약에 「우리 자산 서술자 목록을 문서화하고 머지 후 재확인한다」 상시 조항을 «넣지 않는다».** +> ★**대신 «구멍 하나»를 막는다 — `CLAUDE.md` §Definition of Done 이 CI 명령 5줄 + toolchain 축 1줄 = «6줄»을 축약 없이 싣는다.** + +★**[2026-09-04 정정 — 게이트②] 초판 결정문은 「그물이 없던 자리 «하나» … CI 명령 5줄」이었다** — +계수 «1» 시절 문면이다. 계수가 **2**가 되며 빠진 것은 «줄 하나»가 아니라 **두 축**(target · toolchain)이고 DoD 는 **6줄**이다. + +**⒜ ★계수가 «2»인데도 왜 여전히 조항이 아닌가**(★초판 논거 「1개니까」는 계수 정정으로 **무효** — 결론은 같고 이유가 다르다): +- ★★**② 와 ⑦ 은 «다른 자산»이 아니라 «한 근인의 두 얼굴»이다 — «로컬 DoD 가 CI 매트릭스를 재현하지 않는다».** + ②는 빠진 `- run:` 줄(**target** 축) · ⑦은 빠진 **toolchain** 축(beta). + ⇒ ★**근인 하나를 고치면 둘 다 그물을 얻는다**(실측: DoD 에 `+beta` 를 넣자 ⑦이 로컬에서 RED — rc=101 · 진단 **6**건 — 으로 잡힌다). +- ★**「조항이 «여럿»이면?」도 기각한다** — 세 방향은 잡는 그물이 각각 달라 조항이 셋이 되고, + 그중 둘은 ★**이미 기계가 지키는 자리에 사람 확인을 얹는 것**이다. +- ★**사람 기억에 맡기지 않는다** — 추가한 것은 «체크리스트 항목»이 아니라 ★**실제로 도는 명령**이다. + 회차가 DoD 를 돌리면 **cargo 가 스스로 낡음을 말한다**(`excluded package(s) … not found`). +- ★**목록을 만들지 않았다** — 목록은 사람이 유지해야 하고, 위 표대로 **여섯 자리는 이미 기계가 지킨다**. + +**⒞ 적은 자리 — 워크로그에만 적지 않았다**: +| 자리 | 무엇을 | 왜 그 자리인가 | +|---|---|---| +| ★`CLAUDE.md` §Definition of Done | ★**6줄 verbatim**(CI `- run:` 5줄 + `cargo +beta` 1줄) + 「축약하지 마라」·「toolchain 축을 빼지 마라」와 그 이유(S8 실사고 · beta 실측) | ★**모든 회차가 반드시 읽는다**(DoD) | +| ★`docs/upstream-sync-approach.md` §4 뒤 | **결정문 · 돌연변이 표 · 사료 표 · 재개 조건과 세는 명령** | ★**동기 회차가 반드시 읽는 계약 파일** | + +## 5. ★★재개 조건 + 세는 명령 + 오늘의 값 (계약 4) + +> ★**CI 가 치는 검사 중 로컬 DoD 에 없는 것이 «1건이라도» 생기면 이 결정을 다시 연다.** + +```sh +cd "$(git rev-parse --show-toplevel)" +DOD=$(sed -n '/## Definition of Done/,/^- 착수·완료마다/p' CLAUDE.md) + +# ★축① — CI 의 `- run:` 줄 ∖ 로컬 DoD (0 이어야 한다) +LC_ALL=C /usr/bin/grep -E '^[[:space:]]+- run: ' .github/workflows/rust.yml | sed 's/^[[:space:]]*- run: //' \ +| while IFS= read -r c; do printf '%s' "$DOD" | LC_ALL=C /usr/bin/grep -qF -- "$c" || echo "MISSING-RUN: $c"; done \ +| /usr/bin/grep -c . + +# ★축② — CI 의 toolchain 매트릭스 ∖ 로컬 DoD (0 이어야 한다) +LC_ALL=C /usr/bin/grep -E '^[[:space:]]+rust: \[' .github/workflows/rust.yml \ +| sed 's/.*\[//; s/\].*//; s/, */\n/g' \ +| while IFS= read -r tc; do + [ "$tc" = stable ] && { printf '%s' "$DOD" | LC_ALL=C /usr/bin/grep -qE 'cargo (fmt|clippy|test)' || echo "MISSING-TC: $tc"; continue; } + printf '%s' "$DOD" | LC_ALL=C /usr/bin/grep -qF -- "cargo +$tc " || echo "MISSING-TC: $tc" + done | /usr/bin/grep -c . +``` + +★**[2026-09-04 정정 — 게이트②] 초판은 이 블록에 «축① 만» 실어 놓고 아래 표에서는 두 축을 말했다.** +★**정본은 `docs/upstream-sync-approach.md` §4 이고, 이제 두 파일이 같은 명령을 싣는다.** + +| | 값 | +|---|---| +| 착수 시 | 축① ★**5** · 축② ★**1**(beta 축이 DoD 에 없었다) | +| ★**오늘(정정 후)** | 축① ★**0** · 축② ★**0** | + +★**두 축을 «둘 다» 세라 — 하나만 0 이면 나머지 차원이 조용히 빠진다**(그것이 ⑦이 CI 에서만 났던 이유다). +★**1 이상이 되면**: ⑴그 줄/축을 DoD 에 넣거나 ⑵넣을 수 없는 이유를 적고 **이 결정을 재검토**하라. +★**OS 축(3종)은 로컬 재현 불가라 대조 대상이 아니다** — ★그 차원은 **CI 가 유일한 그물**이고 «알고 두는» 값이다. +★**「조항이 필요하다」로 바로 가지 마라 — 그때도 «먼저 세라»**(위 §1 돌연변이 표를 다시 만들면 된다). + +## 6. 계약 5 — CI 크레이트 이름 1건은 ★**이미 고쳐져 있었다** + +티켓은 「이 회차에서 고쳐도 된다(XS)」고 열어 뒀으나 ★**S8 회차가 이미 고쳐 착지시켰다** +(`.github/workflows/rust.yml:55` = `--exclude test-utils`). ⇒ **이 회차는 그 파일을 안 건드렸다.** +★**그리고 티켓 경고대로 «고쳤으니 조항 결정을 건너뛴다»로 가지 않았다** — 위 §4 가 그 별도 산출이다. +★**이 회차가 한 것은 «그 사고가 로컬에서 안 잡힌 이유»를 막은 것**이고, 그 둘은 다른 층이다 +(전자는 **증상 1건**, 후자는 **그 증상이 늦게 드러난 경로**). + +## 7. 계약 6 — 기존 스위트 전건 · 새 red 0 + +★**정정 후 DoD «6줄»을 «문면 그대로»** 실행(★초판은 **5줄**이었다 — `+beta` 가 빠져 있었다): + +| 명령 | rc | +|---|---| +| `cargo fmt --all -- --check` | **0** | +| `cargo clippy --all -- -D warnings` | **0** | +| ★`cargo +beta clippy --all -- -D warnings` | ★**0** | +| `cargo clippy --workspace --exclude test-utils --target wasm32-unknown-unknown -- -D warnings` | **0** | +| `cargo test --all` | **0** — ★**554 / 0 / 1** | +| `python3 scripts/check-worklog-json.py` | **0** | + +★**새 red 0**(baseline **554/0/1** 동수) · ★**돌연변이는 전부 복구**했고 ★**`.rs` 변경은 0**이다. +★**[fix3 정정] 초판은 「워킹트리는 문서 2파일만」이라 적었다** — 그 회차의 워킹트리 기준으로는 참이었으나, +★**이 PR 이 착지시키는 것은 문서 «6파일»**(규범 2 + 기록 4)이다. ★**같은 문장을 `verification` 에선 고치고 여기엔 남겼던 것**을 이제 닫는다. + +## 8. 경계 준수 + +★**upstream 동기 회차를 «새로 열지» 않았다**(behind **0** · S9 없음) · 포크 정책 무접촉 · +★**「목록 통째 문서화」로 시작하지 않았다**(먼저 셌다) · 머지 **0** · force-push **0** · 리베이스 **0** · +`main` 직접 push **0** · 시크릿 출력 **0** · 코드(`.rs`) 변경 **0** · 맨 `grep` **0**(전건 `/usr/bin/grep`).