From aa01f7ef7fa0ad8dc881d5dac448ed1724f4b631 Mon Sep 17 00:00:00 2001 From: jun0 Date: Sat, 19 Sep 2026 02:45:32 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[rustjava-loadable-set-from-loader-vs-reder?= =?UTF-8?q?ive-decision]=20docs:=20=EC=A0=81=EC=9E=AC=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=20=EC=A7=91=ED=95=A9=EC=9D=80=20=C2=AB=EC=9E=AC=EC=9C=A0?= =?UTF-8?q?=EB=8F=84=C2=BB=EB=A5=BC=20=EC=9C=A0=EC=A7=80=ED=95=9C=EB=8B=A4?= =?UTF-8?q?=20=E2=80=94=20=EB=A1=9C=EB=8D=94=EC=97=90=EC=84=9C=20=EC=9D=BD?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 채택 제안 2026-09-18-named-exception-classes-are-loadable#p2. 순수 결정 회차 — .rs 0줄 · scripts/ 0줄. 전제부터 확인했다: 「4결함 중 3이 재유도」는 참이다(고침 커밋 89c2e83c 에서 갈랐다 — loadable 3 · named 1). ★그런데 그 4번째가 결정한다: named(코드가 무엇을 넘기는가)는 로더가 답할 수 없어, 로더 읽기는 «파서 둘 중 하나»를 없앨 뿐 파싱을 못 없앤다. 실제로 그 절반에서 하루 만에 다음 결함이 났다(형제 회차의 「주석 속 토큰」). 대가 실측: 열거 API 0개(268 등재가 함수 안 지역 배열) ⇒ 검사기 전용 «생산 API» 가 필요하고, 현행 0.8초·무빌드인데 rust.yml 5잡 중 4잡이 그 형상이다(툴체인은 rust_ci 뿐). 결정적 실측: 초판 검사기를 지금 트리에서 돌리면 265 파싱 / 263 이름 ↔ 등재 줄 268 ⇒ 불변식 하나로 결함 2건이 1회차에 잡혔을 값이다(현행 268/268/268). 독립성·0.8초·무빌드를 내주지 않고 얻으므로 후속 카드로 남겼다. ★단 모든 오귀속을 잡지는 않는다(과소계수 계급만). 브리프 전제 1건이 이 repo 에선 거짓이다: machine-independence-guard 는 없다(다른 repo 축). 재개 조건 사전 등록: loadable 재유도 경로에서 «세 번째» 결함이 나오면 다시 연다. 「고침 이후 0건」은 하루짜리 증거라 논거로 쓰지 않았다. --- REPORT.md | 12 +++ STATE.md | 7 ++ docs/loadable-set-source-of-truth.md | 93 +++++++++++++++++++ ...26-09-19-loadable-set-source-of-truth.json | 52 +++++++++++ ...2026-09-19-loadable-set-source-of-truth.md | 78 ++++++++++++++++ 5 files changed, 242 insertions(+) create mode 100644 docs/loadable-set-source-of-truth.md create mode 100644 docs/worklog/2026-09-19-loadable-set-source-of-truth.json create mode 100644 docs/worklog/2026-09-19-loadable-set-source-of-truth.md diff --git a/REPORT.md b/REPORT.md index 9f732458..0c0a7a4f 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,4 +1,16 @@ # REPORT +## [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건**. +- ★★**그런데 그 4번째가 결정한다 — 로더에서 읽어도 «그 절반»은 못 없앤다.** `named`(코드가 `Jvm::exception` 에 «무엇을 넘기는가»)는 **로더가 답할 수 없다** — 소스를 읽는 것 말고는 알 길이 없다. ⇒ ★로더 읽기는 **파서 둘 중 하나를 없앨 뿐 파싱을 없애지 못한다**. 실제로 그 절반에서 **다음 결함이 하루 만에 또 났다**(형제 회차가 잡은 「주석 속 토큰을 센다」). +- ★**대가 실측 둘**: ⒜★**검사기 전용 «생산 API» 가 필요하다** — `get_runtime_class_proto` 는 268 등재를 **함수 «안» 지역 배열**로 만들어 `.find(|p| p.name == name)` 로 쓴다. ★**열거 API 는 0개**(실측) ⇒ 런타임이 검사기를 위해 export 를 갖거나, 테스트가 목록을 다시 적어 **진실원이 다시 둘**이 된다. ⒝★**빌드 없는 검사에 빌드가 붙는다** — 현행 **0.75/0.96/0.77초** · CI 잡은 checkout + `python3` 두 줄이고 ★`rust.yml` **5잡 중 4잡이 그 형상**(툴체인 필요한 것은 `rust_ci` 하나뿐)이다. +- ★★**결정적 실측 — 그 실패는 «공짜로» 잡힌다**: 재유도 버그는 «파스가 짧게 나온다»는 지문을 갖고, 불변식 하나(`등재 줄 수 == 파싱된 등재 == 해석된 이름 수`)가 그것을 본다. **초판 검사기(`38cbab7e`)를 지금 트리에서 돌리니 265 / 263 ↔ 등재 줄 268** ⇒ ★**결함 ⑴⑶ 이 1회차에 그 자리에서 잡혔을 값이다**(현행은 268/268/268 통과). ⇒ 독립성·0.8초·무빌드를 **하나도 내주지 않고** 얻는다. + ★**과장하지 않는다**: 이 불변식이 «모든 오귀속»을 잡지는 «않는다» — 틀리되 서로 다른 이름으로 매핑되면 268 을 유지한다. 잡는 것은 **과소계수 계급**이고, 실측된 거짓 초록 둘이 정확히 그 계급이었다. +- ★**브리프의 전제 1건이 이 repo 에선 «거짓»이다**(적어 둔다): 「이 repo 의 `machine-independence-guard` 가 그 축」 — ★**RustJava 엔 그런 가드가 없다**(`git ls-files | grep -i machine-independence` 빈 출력 · `rust.yml` 5잡 전수 확인). 그것은 **다른 repo 의 축**이다. ⇒ 여기서 잰 ⒜ 의 대가는 그것이 아니라 **툴체인·빌드 의존**이다. +- ★**되돌릴 조건(사전 등록)**: 제안이 스스로 댄 계수 — 「재유도 결함이 몇 개 더 나오는가」. ★**오늘 값은 «고침 이후 0»이고 그것은 «하루»짜리 증거라 논거로 쓰지 않았다.** ⇒ ★**loadable 재유도 경로에서 «세 번째» 결함이 나오면 이 결정을 다시 연다**(`named` 스캔 결함은 세지 마라 — 로더 읽기가 고치는 자리가 아니다). +- 검증: DoD 9명령 · 아래 절. +- ★후속 추천: **재유도가 «짧게 나왔는지»를 단언할 것인가**(S · 위 불변식 — 이 회차가 값을 쟀고 짓지는 않았다). 상세 = `docs/worklog/2026-09-19-loadable-set-source-of-truth.md`. + ## [2026-09-18] 리터럴이 «아닌» 이름으로 exception() 을 부르는 자리는 몇 개인가 — ★**0 이다**(rustjava-count-nonliteral-exception-call-sites) - 무엇을: 채택 제안 `2026-09-18-named-exception-classes-are-loadable#p0`(worklog json `adoptedProposals` 기록). ★**순수 측정 회차 — `.rs` 0줄 · `scripts/` 0줄.** 산출은 «수»와 «술어»다. - ★**답**: bare `exception(` **847** = 정의 **1** + ★**리터럴(java/javax) 846** + 리터럴(그 밖) **0** + ★★**비리터럴 «0»**. diff --git a/STATE.md b/STATE.md index 410577b6..79e475df 100644 --- a/STATE.md +++ b/STATE.md @@ -7,6 +7,13 @@ (둘 다 이것보다 오래됐고 MERGEABLE/CONFLICTING 처분이 이미 걸려 있다). 겹침은 전부 **append 형 합집합**이라 해소는 기계적이다) ## 완료 +- [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`(코드가 무엇을 넘기는가)는 **로더가 답할 수 없다** ⇒ 로더 읽기는 **파서 하나를 없앨 뿐 파싱을 못 없앤다**(그 절반에서 형제 회차가 하루 만에 「주석 속 토큰」 결함을 또 잡았다). + ★**대가 실측**: 열거 API **0개**(268 등재가 함수 «안» 지역 배열) ⇒ **검사기 전용 생산 API** 가 필요 · 현행 **0.8초·무빌드**인데 `rust.yml` **5잡 중 4잡이 그 형상**이다. + ★★**결정적 실측**: 초판 검사기를 지금 트리에서 돌리면 **265/263 ↔ 등재 줄 268** ⇒ ★불변식 하나로 **결함 2건이 1회차에 잡혔을 값**(현행 268/268/268). ★단 «모든 오귀속»은 못 잡는다(과소계수 계급만). + ★**브리프 전제 1건이 거짓**: 이 repo 엔 `machine-independence-guard` 가 **없다**(다른 repo 축). + ★**재개 조건 사전 등록**: loadable 재유도 경로에서 **세 번째** 결함이 나오면 다시 연다(「고침 이후 0」은 하루짜리라 논거로 쓰지 않았다). - [rustjava-count-nonliteral-exception-call-sites] ★★**사각의 «크기»를 쟀다 — 비리터럴 exception() 호출부는 «0» 이다.** 채택 제안 `2026-09-18-named-exception-classes-are-loadable#p0`. ★**순수 측정 · `.rs` 0줄 · `scripts/` 0줄.** ★**수**: bare `exception(` **847** = 정의 1 + **리터럴 846** + 그 밖 리터럴 **0** + ★**비리터럴 0** ⇒ 검사기가 보는 집합 = 실제 호출부 집합(지금은 일치). ★★**술어를 갈라야 답이 맞는다** — `exception(` 부분일치가 `assert_exception(` 등 **다른 함수 8종 41자리**(첫 인자가 `jvm`)를 쓸어담는다. 안 갈랐으면 ★**M=33 이라는 틀린 답**이었다. diff --git a/docs/loadable-set-source-of-truth.md b/docs/loadable-set-source-of-truth.md new file mode 100644 index 00000000..f2418669 --- /dev/null +++ b/docs/loadable-set-source-of-truth.md @@ -0,0 +1,93 @@ +# Should the loadable set be read from the loader instead of re-derived? + +**Decision: no. `check-named-exception-classes-are-loadable.py` keeps parsing the Rust source.** +The proposal's premise is correct — three of the four defects really were in the re-derivation — but +reading from the loader buys less than it looks and costs a production API plus a build dependency, +and the failure mode it aims at is detectable for nothing without giving either up. + +Adopted proposal: `2026-09-18-named-exception-classes-are-loadable#p2`. Measured 2026-09-19 against +`origin/main` @ `ddc6c4ce`. + +## First: is "three of four" true? + +It is. Read from the fix commit `89c2e83c`, not from the prose about it — four distinct defects, and +which half of the checker each lived in: + +| # | defect | fixed by | half | +|---|---|---|---| +| 1 | only `as_proto()` matched, so three `list_proto()` registrations read as absent | `REGISTERED` regex gains `(?:as\|list)_proto` | **loadable (re-derivation)** | +| 2 | first `name:` in an `impl` block attributed the wrong class when a type holds two proto constructors | `IMPL_BLOCK` → `IMPL_START` + `PROTO_FN`, per function | **loadable (re-derivation)** | +| 3 | bare type names as keys, so one of a colliding pair answered for its twin | key becomes `(module, type, function)` | **loadable (re-derivation)** | +| 4 | line-by-line scan missed 34 calls rustfmt had broken across a newline | `NAMED.finditer(line)` → `finditer(text)` | **named (call-site scan)** | + +So 3/4, as claimed. What the claim does *not* say, and what decides this: **the fourth is in the half +that reading the loader cannot remove.** + +## Why reading from the loader buys less than it looks + +The check compares two sets. Reading the loader replaces one of them: + +- `loadable` — which classes the runtime can resolve. This *could* come from the runtime. +- `named` — which class names the Rust code passes to `Jvm::exception`. This **cannot**. There is no + way to learn it except by reading the source, short of executing all 846 call sites. + +Defect 4 was in `named`, and that half keeps every hazard that made it: 846 call sites, rustfmt +splitting calls across lines, and — found the next day by +`2026-09-18-nonliteral-exception-call-sites` — the same anchor matching its own token inside a +**comment**. Reading the loader removes three defects' worth of parsing and leaves the parser. + +## What it would cost + +**A production API that exists only for the check.** `get_runtime_class_proto` builds its 268 +registrations as a **local array inside the function** and consumes it with +`protos.into_iter().find(|proto| proto.name == name)`. There is no enumeration — measured: zero +public functions returning the proto list. So emitting the names means either exporting that array +from `rustjava-runtime`, or writing a test that restates the list, which re-creates the two-sources +problem the proposal is trying to remove. + +**A build dependency on a check that has none.** Measured: the checker runs in **0.75–0.96 s** on +nothing but source text. Its CI job is five lines — checkout, `python3 script`. Four of the five jobs +in `rust.yml` are that shape (`worklog_json`, `merge_drops`, `dod_parity`, +`named_exception_classes`); only `rust_ci` needs a toolchain. Reading from the loader moves this +check across that line, in CI and in the local DoD both. + +The proposal names the drift risk itself: a generated list goes stale when the emitter is not re-run. +This repo already runs that pattern once — `test-data/class-file-versions.txt` with +`record-class-file-versions.py` — and `AGENTS.md` has to spell out that adding a fixture means +editing the freeze file *in the same commit*, because otherwise it silently rots. + +## The deciding measurement: the failure mode is cheap to catch anyway + +The proposal's fear is that a re-derivation bug produces a false green. That bug has a signature — +the parse comes out *short* — and one invariant sees it: + +``` +registration lines in loader.rs == registrations the checker parsed == distinct names it resolved +``` + +Run against both versions of the checker on today's tree: + +| checker | parsed registrations | distinct names | vs 268 registration lines | +|---|---|---|---| +| first draft (`38cbab7e`) | **265** | **263** | **breaks — caught on the spot** | +| current | 268 | 268 | passes | + +So defects 1 and 3 would have been caught in the first round, by a check that keeps the +independence, the 0.8 s, and the zero build steps. That is not built here — the adopted proposal +asked for a decision, not a third mechanism — and is filed as a follow-up. + +*Not claimed*: that this invariant catches every mis-attribution. A registration mapped to a wrong +but still distinct name can keep the count at 268. It catches the undercount class, which is what +both measured false greens were. + +## What would reopen this + +The proposal states the deciding count itself: *"how many more re-derivation defects show up — if +this round was the last one, the parsing version is cheaper."* Today that count is **0 since the fix +landed**, and that number is worth exactly what one day of evidence is worth — which is why it is not +the argument above. + +**Re-measure at the third defect.** If a third defect is found in the loadable re-derivation (the +`REGISTERED` / `PROTO_FN` / keying path — not the `named` scan, which reading the loader does not +fix), this decision reopens and the emitter becomes the cheaper option. Count them the same way this +document did: from the fix commits, classified by which half they lived in. diff --git a/docs/worklog/2026-09-19-loadable-set-source-of-truth.json b/docs/worklog/2026-09-19-loadable-set-source-of-truth.json new file mode 100644 index 00000000..de85d80e --- /dev/null +++ b/docs/worklog/2026-09-19-loadable-set-source-of-truth.json @@ -0,0 +1,52 @@ +{ + "date": "2026-09-19", + "taskId": "rustjava-loadable-set-from-loader-vs-rederive-decision", + "summary": "Decision round, no code changed. Keep re-deriving the loadable set from Rust source rather than reading it from the loader. The proposal's premise checks out - 3 of the 4 defects were in the re-derivation - but reading the loader removes one of the two parsers and not the parsing, needs a production API that only the check would use, and puts a build under a check that costs 0.8s with none. The failure mode it targets is catchable by a single invariant that would have broken in round 1.", + "decision": "keep re-derivation; do not read the loadable set from the loader", + "measurements": { + "defects_total": 4, + "defects_in_loadable_rederivation": 3, + "defects_in_named_scan": 1, + "loader_registration_lines": 268, + "prefix_checker_parsed_registrations": 265, + "prefix_checker_distinct_names": 263, + "current_checker_parsed_registrations": 268, + "current_checker_distinct_names": 268, + "checker_runtime_seconds": [0.96, 0.75, 0.77], + "public_enumeration_apis_for_protos": 0, + "ci_jobs_total": 5, + "ci_jobs_needing_toolchain": 1, + "rederivation_defects_since_the_fix": 0, + "days_of_that_evidence": 1 + }, + "verification": [ + "the 3-of-4 claim was checked against fix commit 89c2e83c itself, not the prose: REGISTERED as_proto->(as|list)_proto, IMPL_BLOCK->IMPL_START+PROTO_FN, bare-name key->(module,type,function) are loadable-side; NAMED.finditer(line)->finditer(text) is named-side", + "invariant test: ran the pre-fix checker (38cbab7e) against today's tree - parsed 265 registrations, resolved 263 names, against 268 registration lines in loader.rs; current checker gives 268/268/268", + "no enumeration API: get_runtime_class_proto holds the 268 protos in a local array consumed by .find(|proto| proto.name == name); grep for public functions returning the proto list returns nothing", + "checker cost measured 3x with /usr/bin/time; CI job shapes read from rust.yml (4 of 5 jobs are checkout + python3, only rust_ci needs a toolchain)" + ], + "changes": [ + "docs/loadable-set-source-of-truth.md - the decision, its measurements and the re-measure trigger", + "docs/worklog/2026-09-19-loadable-set-source-of-truth.{md,json}, REPORT.md, STATE.md", + "no .rs and no scripts/ changes" + ], + "issues": [ + "The brief's machine-independence premise does not hold for this repo: RustJava has no machine-independence-guard (git ls-files grep is empty; rust.yml has five jobs). That axis belongs to another repo, so the cost weighed here is the toolchain/build dependency instead.", + "The '0 re-derivation defects since the fix' count is one day old and is deliberately not used as the argument; the re-measure trigger is stated instead.", + "The invariant that would have caught defects 1 and 3 is not claimed to catch every mis-attribution: a registration mapped to a wrong but distinct name keeps the count at 268." + ], + "adoptedProposals": [ + "2026-09-18-named-exception-classes-are-loadable#p2" + ], + "proposals": [ + { + "title": "Assert the loadable re-derivation did not come up short, instead of trusting it", + "plainSummary": "The safety check works out which classes the runtime can load by reading the source. When that reading quietly misses some, the check still says everything is fine - which is the one thing it must never do wrongly.", + "userBenefit": "A silent miscount in the check would go back to being visible immediately rather than waiting for someone to read the script alongside the source.", + "why": "Measured in this round: the first draft of the checker parsed 265 registrations and resolved 263 names against 268 registration lines in loader.rs. One invariant - registration lines == parsed registrations == distinct names - breaks on both of those, so two of the three loadable-side defects would have been caught in round 1 without giving up source independence, the 0.8s runtime, or the build-free CI job. This round decided against reading the loader; this is the cheap half of what that would have bought.", + "tradeoff": "It adds a third number the checker has to keep true, and it is a floor rather than a proof: a registration mapped to a wrong but still distinct name keeps the count at 268 and passes. It also couples the check to the textual shape of loader.rs (one registration per line), which is true today and is not guaranteed.", + "effort": "S", + "target": "scripts/check-named-exception-classes-are-loadable.py" + } + ] +} diff --git a/docs/worklog/2026-09-19-loadable-set-source-of-truth.md b/docs/worklog/2026-09-19-loadable-set-source-of-truth.md new file mode 100644 index 00000000..7aa58a57 --- /dev/null +++ b/docs/worklog/2026-09-19-loadable-set-source-of-truth.md @@ -0,0 +1,78 @@ +# 2026-09-19 — Read the loadable set from the loader, or keep re-deriving it? + +Round: `rustjava-loadable-set-from-loader-vs-rederive-decision` +Adopted proposal: `2026-09-18-named-exception-classes-are-loadable#p2` + +**This is a decision round. No code changed** — no `.rs`, no `scripts/`. The output is a decision, the +measurements behind it, and a re-measure trigger. Full reasoning: +`docs/loadable-set-source-of-truth.md`. + +## Decision + +**Keep re-deriving from source.** Do not read the loadable set from the loader. + +## The premise was checked first, and it holds + +The proposal rests on one number — *"three of this round's four defects came from that +re-derivation"* — so it was verified against the fix commit `89c2e83c` rather than the prose: + +| # | defect | half | +|---|---|---| +| 1 | only `as_proto()` matched → three `list_proto()` registrations read as absent | loadable | +| 2 | first `name:` in an `impl` attributed the wrong class when a type has two proto constructors | loadable | +| 3 | bare type names as keys → one of a colliding pair answered for its twin | loadable | +| 4 | line-by-line scan lost 34 calls rustfmt had split across a newline | **named** | + +**3/4 — true as stated.** + +## Why the answer is still "keep re-deriving" + +1. **It removes one of the two parsers, not the parsing.** `named` — which class names the Rust code + passes to `Jvm::exception` — cannot come from the loader; there is no way to know it but to read + the source. Defect 4 lived there, and so did the next one found: the sibling round + (`2026-09-18-nonliteral-exception-call-sites`) hit the same anchor matching its own token **inside + a comment** a day later. +2. **It needs a production API that only the check would use.** `get_runtime_class_proto` builds its + 268 registrations as a **local array inside the function** and consumes them with + `.find(|proto| proto.name == name)`. Measured: **zero** public functions enumerate the protos. So + the runtime would grow an export to serve a checker — or a test would restate the list, which is + the two-sources problem again. +3. **It puts a build under a check that has none.** Measured **0.75 / 0.96 / 0.77 s**, source text + only. Its CI job is checkout + `python3 script`; **4 of the 5 jobs** in `rust.yml` are that shape, + and only `rust_ci` needs a toolchain. +4. **The failure mode is catchable for free.** See below. + +## The measurement that decided it + +A re-derivation bug produces a *short* parse, and one invariant sees that: +`registration lines == parsed registrations == distinct names resolved`. + +| checker | parsed | names | vs 268 lines in `loader.rs` | +|---|---|---|---| +| first draft `38cbab7e` | **265** | **263** | **breaks — would have been caught in round 1** | +| current | 268 | 268 | passes | + +Defects 1 and 3 were visible without giving up independence, the 0.8 s, or the zero build steps. +Filed as a follow-up rather than built here, because the adopted proposal asked for a decision. + +*Not claimed*: that the invariant catches every mis-attribution — a registration mapped to a wrong +but distinct name keeps the count at 268. It catches the undercount class, which is what both +measured false greens were. + +## A premise in the brief that does not hold for this repo + +The brief warned that reading from the loader might break **machine independence**, citing *"이 repo 의 +`machine-independence-guard` 가 그 축이고 오늘 그 잡 하나로 전 PR 이 red 였다"*. Measured: **RustJava has no +such guard** — `git ls-files | grep -i machine-independence` is empty, and `rust.yml` has exactly five +jobs (`rust_ci`, `worklog_json`, `merge_drops`, `dod_parity`, `named_exception_classes`). That axis +belongs to a different repo. The real cost of ⒜ here is the toolchain/build dependency in (3), which +is what this decision weighed instead. + +## Re-measure trigger + +The proposal names the deciding count: *"how many more re-derivation defects show up."* Today it is +**0 since the fix landed** — one day of evidence, which is why it is not the argument. + +**Reopen at the third defect** found in the loadable re-derivation path (`REGISTERED` / `PROTO_FN` / +keying) — not in the `named` scan, which reading the loader does not fix. Count from the fix commits, +classified by half, exactly as the table above does. From 0b8a18c182c2c80a3b9743b75ffc1f1513e342b3 Mon Sep 17 00:00:00 2001 From: jun0 Date: Sat, 19 Sep 2026 04:22:51 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[rustjava-loadable-set-from-loader-vs-reder?= =?UTF-8?q?ive-decision-fix]=20docs:=20=EA=B2=8C=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=E2=91=A1=20=EB=B0=98=EB=A0=A4=20=EC=8A=B9=EA=B3=84=20=E2=80=94?= =?UTF-8?q?=20=ED=98=95=EC=A0=9C=20=EC=9D=B8=EC=9A=A9=20=EC=A0=95=EC=A0=95?= =?UTF-8?q?=EA=B3=BC=20=EB=B6=88=EB=B3=80=EC=8B=9D=20=EB=B2=94=EC=9C=84=20?= =?UTF-8?q?=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F1 — 형제 회차 인용이 인용원과 반대였다. 그 회차 worklog 는 주석을 「0 on a commented-out line」, 즉 «측정된 0» 으로 적는다. 실제 앵커 함정은 주석이 아니라 «다른 함수 8종 41자리» (`assert_exception(`·`suppress_io_exception(` 등 · 첫 인자가 jvm)이고, 세면 33 · 맞는 답은 0 이다. 간격도 「하루 만에」가 아니라 실측 4시간 31분(89c2e83c 19:49 → 128e0fe5 00:21 · 캘린더로만 «다음 날»). 네 자리 전부 고쳤다: 결정 문서 · worklog md · REPORT.md · STATE.md. ★이 회차의 자기 규율(「산문이 아니라 커밋에서 갈랐다」)이 바로 그 자리에서 안 지켜졌고, 같은 커밋의 STATE.md 가 6줄 아래에서 같은 사실을 옳게 적고 있었다 — 그 사실을 worklog 에 한 절로 남겼다. F2 — 「both measured false greens」는 과대주장이었다. 불변식의 세 항이 전부 loader.rs + classes/ 에서 나오므로 named 수를 아예 보지 않는다 ⇒ 거짓 초록 둘 중 하나(결함 3 = 짧은 이름 키 충돌)만 잡고 결함 4(다중 줄 · named 쪽 812↔846)는 못 잡는다. 잡는 것은 «거짓 초록 1 + 거짓 빨강 1» 이다. 세 자리를 고쳤다(브리프는 두 자리라 했으나 worklog md 에 같은 문장 사본이 있었다). ★본문 표(defects 1 and 3)와 worklog json 은 정확하므로 무접촉이다(numstat 0). §12-5 — 「Its CI job is five lines」는 named_exception_classes 잡(이 문서의 주어)이고 실측 5줄로 참이다. merge_drops 가 7줄인 것은 다른 잡이다. 그러나 검수자가 실제로 오독했으므로 주어를 명시하고 「그 넷의 공통점은 길이가 아니라 툴체인 불요」임을 한 괄호로 못박았다. 측정·논증·결정 무접촉 · 코드 0행(.rs 0 · scripts/ 0). --- REPORT.md | 4 +-- STATE.md | 2 +- docs/loadable-set-source-of-truth.md | 25 +++++++++++++------ ...2026-09-19-loadable-set-source-of-truth.md | 25 +++++++++++++++---- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/REPORT.md b/REPORT.md index 0c0a7a4f..99da34ae 100644 --- a/REPORT.md +++ b/REPORT.md @@ -2,10 +2,10 @@ ## [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건**. -- ★★**그런데 그 4번째가 결정한다 — 로더에서 읽어도 «그 절반»은 못 없앤다.** `named`(코드가 `Jvm::exception` 에 «무엇을 넘기는가»)는 **로더가 답할 수 없다** — 소스를 읽는 것 말고는 알 길이 없다. ⇒ ★로더 읽기는 **파서 둘 중 하나를 없앨 뿐 파싱을 없애지 못한다**. 실제로 그 절반에서 **다음 결함이 하루 만에 또 났다**(형제 회차가 잡은 「주석 속 토큰을 센다」). +- ★★**그런데 그 4번째가 결정한다 — 로더에서 읽어도 «그 절반»은 못 없앤다.** `named`(코드가 `Jvm::exception` 에 «무엇을 넘기는가»)는 **로더가 답할 수 없다** — 소스를 읽는 것 말고는 알 길이 없다. ⇒ ★로더 읽기는 **파서 둘 중 하나를 없앨 뿐 파싱을 없애지 못한다**. 실제로 그 절반에서 **다음 결함이 «4시간 31분» 뒤에 또 났다**(`89c2e83c` 19:49 → `128e0fe5` 00:21 — ★「하루 만에」는 **캘린더 기준으로만** 참이다): ★**같은 앵커가 «다른 함수 8종»을 쓸어담는다** — `exception(` 이 `assert_exception(`·`suppress_io_exception(` 등의 부분문자열이라 **41자리**(첫 인자가 `jvm`)가 함께 걸리고, ★**세면 «33», 맞는 답은 «0»** 이다. - ★**대가 실측 둘**: ⒜★**검사기 전용 «생산 API» 가 필요하다** — `get_runtime_class_proto` 는 268 등재를 **함수 «안» 지역 배열**로 만들어 `.find(|p| p.name == name)` 로 쓴다. ★**열거 API 는 0개**(실측) ⇒ 런타임이 검사기를 위해 export 를 갖거나, 테스트가 목록을 다시 적어 **진실원이 다시 둘**이 된다. ⒝★**빌드 없는 검사에 빌드가 붙는다** — 현행 **0.75/0.96/0.77초** · CI 잡은 checkout + `python3` 두 줄이고 ★`rust.yml` **5잡 중 4잡이 그 형상**(툴체인 필요한 것은 `rust_ci` 하나뿐)이다. - ★★**결정적 실측 — 그 실패는 «공짜로» 잡힌다**: 재유도 버그는 «파스가 짧게 나온다»는 지문을 갖고, 불변식 하나(`등재 줄 수 == 파싱된 등재 == 해석된 이름 수`)가 그것을 본다. **초판 검사기(`38cbab7e`)를 지금 트리에서 돌리니 265 / 263 ↔ 등재 줄 268** ⇒ ★**결함 ⑴⑶ 이 1회차에 그 자리에서 잡혔을 값이다**(현행은 268/268/268 통과). ⇒ 독립성·0.8초·무빌드를 **하나도 내주지 않고** 얻는다. - ★**과장하지 않는다**: 이 불변식이 «모든 오귀속»을 잡지는 «않는다» — 틀리되 서로 다른 이름으로 매핑되면 268 을 유지한다. 잡는 것은 **과소계수 계급**이고, 실측된 거짓 초록 둘이 정확히 그 계급이었다. + ★**과장하지 않는다**: 이 불변식이 «모든 오귀속»을 잡지는 «않는다» — 틀리되 서로 다른 이름으로 매핑되면 268 을 유지한다. 잡는 것은 **과소계수 계급**이되 ★**loadable 쪽에서만**이다 — 세 항(등재 줄·파싱된 등재·해석된 이름)이 **전부 `loader.rs`+`classes/`** 에서 나와 ★**`named` 수를 아예 보지 않는다**. ⇒ 실측된 거짓 초록 «둘» 중 ★**하나만 잡는다**(결함 3 = 짧은 이름 키 충돌) · ★**결함 4는 못 잡는다**(과소계수가 `named` 쪽이다 — 812 ↔ 846). ★잡는 것은 **거짓 초록 1 + 거짓 빨강 1** 이다. - ★**브리프의 전제 1건이 이 repo 에선 «거짓»이다**(적어 둔다): 「이 repo 의 `machine-independence-guard` 가 그 축」 — ★**RustJava 엔 그런 가드가 없다**(`git ls-files | grep -i machine-independence` 빈 출력 · `rust.yml` 5잡 전수 확인). 그것은 **다른 repo 의 축**이다. ⇒ 여기서 잰 ⒜ 의 대가는 그것이 아니라 **툴체인·빌드 의존**이다. - ★**되돌릴 조건(사전 등록)**: 제안이 스스로 댄 계수 — 「재유도 결함이 몇 개 더 나오는가」. ★**오늘 값은 «고침 이후 0»이고 그것은 «하루»짜리 증거라 논거로 쓰지 않았다.** ⇒ ★**loadable 재유도 경로에서 «세 번째» 결함이 나오면 이 결정을 다시 연다**(`named` 스캔 결함은 세지 마라 — 로더 읽기가 고치는 자리가 아니다). - 검증: DoD 9명령 · 아래 절. diff --git a/STATE.md b/STATE.md index 79e475df..88471085 100644 --- a/STATE.md +++ b/STATE.md @@ -9,7 +9,7 @@ ## 완료 - [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`(코드가 무엇을 넘기는가)는 **로더가 답할 수 없다** ⇒ 로더 읽기는 **파서 하나를 없앨 뿐 파싱을 못 없앤다**(그 절반에서 형제 회차가 하루 만에 「주석 속 토큰」 결함을 또 잡았다). + ★★**그 1건이 결정한다**: `named`(코드가 무엇을 넘기는가)는 **로더가 답할 수 없다** ⇒ 로더 읽기는 **파서 하나를 없앨 뿐 파싱을 못 없앤다**(그 절반에서 형제 회차가 ★**4시간 31분** 뒤에 또 잡았다 — ★**「다른 함수 8종 41자리」**를 쓸어담는 앵커 함정이고, 세면 **33** · 맞는 답은 **0** 이다). ★**대가 실측**: 열거 API **0개**(268 등재가 함수 «안» 지역 배열) ⇒ **검사기 전용 생산 API** 가 필요 · 현행 **0.8초·무빌드**인데 `rust.yml` **5잡 중 4잡이 그 형상**이다. ★★**결정적 실측**: 초판 검사기를 지금 트리에서 돌리면 **265/263 ↔ 등재 줄 268** ⇒ ★불변식 하나로 **결함 2건이 1회차에 잡혔을 값**(현행 268/268/268). ★단 «모든 오귀속»은 못 잡는다(과소계수 계급만). ★**브리프 전제 1건이 거짓**: 이 repo 엔 `machine-independence-guard` 가 **없다**(다른 repo 축). diff --git a/docs/loadable-set-source-of-truth.md b/docs/loadable-set-source-of-truth.md index f2418669..a7f95a11 100644 --- a/docs/loadable-set-source-of-truth.md +++ b/docs/loadable-set-source-of-truth.md @@ -32,9 +32,12 @@ The check compares two sets. Reading the loader replaces one of them: way to learn it except by reading the source, short of executing all 846 call sites. Defect 4 was in `named`, and that half keeps every hazard that made it: 846 call sites, rustfmt -splitting calls across lines, and — found the next day by -`2026-09-18-nonliteral-exception-call-sites` — the same anchor matching its own token inside a -**comment**. Reading the loader removes three defects' worth of parsing and leaves the parser. +splitting calls across lines, and — found **4 h 31 min later** by +`2026-09-18-nonliteral-exception-call-sites` (`89c2e83c` 19:49 → `128e0fe5` 00:21, which is "the next +day" only by the calendar) — the same anchor matching **eight other function names**: `exception(` is +a substring of `assert_exception(`, `suppress_io_exception(` and six more, **41 sites** whose first +argument is `jvm` rather than a class name. Counting them would have answered **33** instead of the +correct **0**. Reading the loader removes three defects' worth of parsing and leaves the parser. ## What it would cost @@ -46,9 +49,11 @@ from `rustjava-runtime`, or writing a test that restates the list, which re-crea problem the proposal is trying to remove. **A build dependency on a check that has none.** Measured: the checker runs in **0.75–0.96 s** on -nothing but source text. Its CI job is five lines — checkout, `python3 script`. Four of the five jobs -in `rust.yml` are that shape (`worklog_json`, `merge_drops`, `dod_parity`, -`named_exception_classes`); only `rust_ci` needs a toolchain. Reading from the loader moves this +nothing but source text. Its own job, `named_exception_classes`, is five lines — checkout, `python3 +script`. Four of the five jobs in `rust.yml` need **no toolchain** (`worklog_json`, `merge_drops`, +`dod_parity`, `named_exception_classes`); only `rust_ci` does. (Line counts differ among those four — +`merge_drops` is seven, carrying `fetch-depth: 0` — which is why the shared property named here is the +toolchain, not the length.) Reading from the loader moves this check across that line, in CI and in the local DoD both. The proposal names the drift risk itself: a generated list goes stale when the emitter is not re-run. @@ -77,8 +82,12 @@ independence, the 0.8 s, and the zero build steps. That is not built here — th asked for a decision, not a third mechanism — and is filed as a follow-up. *Not claimed*: that this invariant catches every mis-attribution. A registration mapped to a wrong -but still distinct name can keep the count at 268. It catches the undercount class, which is what -both measured false greens were. +but still distinct name can keep the count at 268. And it catches an undercount only on the +**loadable** side: all three of its terms — registration lines, parsed registrations, resolved names — +are read from `loader.rs` and `classes/`, so it never sees the `named` count at all. Of the two +measured false greens it therefore catches **one** (defect 3, the colliding bare-name key) and misses +defect 4, whose undercount was on the `named` side (812 against 846). What it does catch is one false +green and one false red. ## What would reopen this diff --git a/docs/worklog/2026-09-19-loadable-set-source-of-truth.md b/docs/worklog/2026-09-19-loadable-set-source-of-truth.md index 7aa58a57..3c326b35 100644 --- a/docs/worklog/2026-09-19-loadable-set-source-of-truth.md +++ b/docs/worklog/2026-09-19-loadable-set-source-of-truth.md @@ -29,9 +29,12 @@ re-derivation"* — so it was verified against the fix commit `89c2e83c` rather 1. **It removes one of the two parsers, not the parsing.** `named` — which class names the Rust code passes to `Jvm::exception` — cannot come from the loader; there is no way to know it but to read - the source. Defect 4 lived there, and so did the next one found: the sibling round - (`2026-09-18-nonliteral-exception-call-sites`) hit the same anchor matching its own token **inside - a comment** a day later. + the source. Defect 4 lived there, and so did the next one found **4 h 31 min later** (`89c2e83c` + 19:49 → `128e0fe5` 00:21 — "a day later" only by the calendar): the sibling round + (`2026-09-18-nonliteral-exception-call-sites`) hit the same anchor matching **eight other function + names** — `exception(` is a substring of `assert_exception(`, `suppress_io_exception(` and six + more, **41 sites** whose first argument is `jvm`, not a class name. Counting them would have + answered **33** where the correct answer is **0**. 2. **It needs a production API that only the check would use.** `get_runtime_class_proto` builds its 268 registrations as a **local array inside the function** and consumes them with `.find(|proto| proto.name == name)`. Measured: **zero** public functions enumerate the protos. So @@ -56,8 +59,20 @@ Defects 1 and 3 were visible without giving up independence, the 0.8 s, or the z Filed as a follow-up rather than built here, because the adopted proposal asked for a decision. *Not claimed*: that the invariant catches every mis-attribution — a registration mapped to a wrong -but distinct name keeps the count at 268. It catches the undercount class, which is what both -measured false greens were. +but distinct name keeps the count at 268. And it sees an undercount only on the **loadable** side: all +three of its terms come from `loader.rs` and `classes/`, never from the `named` count. So of the two +measured false greens it catches **one** (defect 3) and misses defect 4, whose undercount was on the +`named` side (812 against 846) — what it catches is one false green and one false red. + +## Where this round's own rule slipped (gate 2, F1) + +The rule this round applied to the "3 of 4" claim — *read it from the commit, not from the prose +about it* — was not applied to the sibling citation. The sibling's worklog says comments were a +measured **zero** ("0 on a commented-out line"); the trap it actually hit was the anchor matching +eight other function names. Worse, the same commit's `STATE.md` described that sibling correctly six +lines further down, so **one commit stated the same fact two ways**. Corrected above and in three +other places. The lesson is not "cite more carefully" but the rule that was already written here: +apply it to *every* claim, including the ones that merely set the scene. ## A premise in the brief that does not hold for this repo