- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
182 lines
14 KiB
Markdown
182 lines
14 KiB
Markdown
---
|
|
kind: CASE
|
|
slug: a14-f015-virtualthreadprofile-propertyname
|
|
title: 켜는 속성이라고 적힌 메서드가 어느 게이트도 쓰지 않는 이름을 돌려준다
|
|
topic: web-inbound-and-http-surface
|
|
project: clean-architecture-backend-template
|
|
status: 게시 전
|
|
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
|
evidenceCapturedOn: 2026-09-03
|
|
rootTreeNode: case:a14-f015-virtualthreadprofile-propertyname
|
|
body: case-a14-f015-virtualthreadprofile-propertyname.body.md
|
|
assets:
|
|
- key: a14-f015-virtualthreadprofile-propertyname
|
|
file: ../../../final/evidence/rendered/a14-f015-virtualthreadprofile-propertyname.svg
|
|
- key: a14-f015-virtualthreadprofile-propertyname-bind
|
|
file: ../../../final/evidence/rendered/a14-f015-virtualthreadprofile-propertyname-bind.svg
|
|
evidence:
|
|
- ../../../final/evidence/raw/a14-f015-virtualthreadprofile-propertyname.txt
|
|
- ../../../final/evidence/raw/a14-f015-virtualthreadprofile-propertyname-bind.txt
|
|
source:
|
|
- 원본 분석 절은 final/document.md#a14#L1279 이고, 세 철자를 비교한 표는 §35.3 이다.
|
|
---
|
|
|
|
# 켜는 속성이라고 적힌 메서드가 어느 게이트도 쓰지 않는 이름을 돌려준다
|
|
|
|
가상 스레드 프로파일에 `propertyName()` 이 있고 자바독이 이것을 켜는 속성이라고 적었다. 그 메서드는 문자열을 그대로 적어 돌려주는데 `mvc-` 접두사가 빠져 있어서, 실제 바인딩과 게이트가 쓰는 이름과 다르다. 그 이름 자체는 저장소에 이 반환문에만 있다. 2026-08-13 계획 문서가 적은 게이트 접두사에 이름 `enabled` 를 합치면 나오는 키가 바로 그 이름이다.
|
|
|
|
## 관계
|
|
|
|
- **선언된 Advanced 능력 열둘 중 속성 이름을 읽는 코드가 있는 것은 둘이다**
|
|
거기서 이 메서드는 속성 이름을 만들기만 하는 둘 중 하나로 세었다. 그 이름이 왜 게이트와 어긋나는지는 여기서 다룬다.
|
|
- **호출자 없이 들어온 메서드이고, 도달 불가라던 가지는 켜지면 반대로 답한다**
|
|
둘 다 부르는 곳이 없어서 지금은 아무 일도 일어나지 않는다. 다만 a14-f002 는 검사 메서드 전체가 그렇고, 여기는 배선된 타입에 붙은 정적 메서드 하나가 그렇다.
|
|
- **선언 순서를 단언하는 테스트는 그 순서를 읽는 코드가 있을 때만 게이트다**
|
|
형제 두 타입에는 이름을 단언하는 시험이 있다. 기대값은 손으로 적은 문자열이거나 상수 개수다. 그 이름을 읽는 코드가 있는지는 어느 쪽도 보지 않는다.
|
|
|
|
## 문제
|
|
|
|
가상 스레드 스위치의 이름이 코드 세 군데에 다른 철자로 나온다고 원문이 적었다. 그 셋을 확인하고, 같은 이름의 메서드를 가진 다른 타입까지 함께 봤다.
|
|
|
|
## 결론
|
|
|
|
같지 않다. VirtualThreadProfile:74-77 만 mvc- 가 없는 이름을 돌려준다.
|
|
|
|
propertyName() 이라는 메서드를 선언하는 타입이 이 저장소에 셋 있다. 웹 능력 열거형과 웹소켓 능력 열거형은 상수 이름에서 문자열을 만들고, 가상 스레드 프로파일만 문자열을 적어 두었다. 적어 둔 그 하나만 static 이고, 틀린 것도 그 하나다.
|
|
|
|
발행된 이름 그대로는 저장소에 한 번, 자기 반환문에만 나온다. 접두사까지만 잘라 찾으면 한 줄이 더 나온다. 2026-08-13 확장 계획 문서가 아직 만들지 않은 설정 클래스를 virtual-threads 로 게이트하라고 적은 자리다. 계획대로 만들었다면 그 게이트가 읽었을 속성이 이 메서드가 돌려주는 이름이다. 구현된 게이트는 mvc-virtual-threads 를 쓴다. 원문은 계획 문서를 언급하지 않는다.
|
|
|
|
코드에서 그 이름을 읽거나 쓰는 자리는 없다. 운영자용 문서 두 편은 맞는 이름을 쓴다. docs/web/advanced-capabilities.md:8 의 지원 표와 docs/web/virtual-thread-profile.md:25 의 yml 예시다.
|
|
|
|
두 이름을 각각 참으로 두고 웹 컨텍스트를 띄웠다. 게이트된 설정 클래스만 올린 구성과, 루트가 이 리프에 거는 패키지를 그대로 스캔한 구성 둘이다. 두 구성의 표는 본문에 있다.
|
|
|
|
출하 쪽 구성에서는 설정 빈이 늘 등록되고, 속성 이름이 좌우하는 것은 게이트다. 발행된 이름은 두어도 결과가 같다. 다만 그 구성은 SecuritySettings 가 요구하는 ca-skeleton.security.issuer-uri 를 채워야 기동한다.
|
|
|
|
이 설정 클래스가 발행하는 설정 메타데이터에도 그 이름은 없다. 고정 소스에 설정 프로세서를 돌려 뽑으면 접두사 backend.web.advanced.mvc-virtual-threads 아래 이름 여섯이 나온다. 리프가 프로세서를 선언하도록 루트 빌드가 리프마다 check 에 걸어 두었으므로, 이 메타데이터는 배포판에 늘 들어간다.
|
|
|
|
한편 이 타입 자체는 배선되어 있다. VirtualThreadSettings:77 이 레코드를 만들고 VirtualThreadMvcConfiguration:40 이 빈으로 등록하며 VirtualThreadSettings:91 이 기동 검증에서 생성자를 다시 부른다. 부르는 곳이 없는 것은 그 정적 메서드 하나뿐이다.
|
|
|
|
원문은 이 건에 권고를 적지 않았다. 같은 절의 권고는 앞 항목인 P2 의 것이다. 이 메서드는 지우거나 실제 게이트 이름을 돌려주게 고치면 된다. 열거형이 같은 능력에 대해 맞는 이름을 계산하므로 그것을 부르면 된다.
|
|
|
|
판정은 P3 이고 원문과 같다. 부르는 곳이 없어 실행 결과가 달라지지 않는다.
|
|
|
|
## 검증 환경
|
|
|
|
OpenJDK : 21.0.12
|
|
Spring Boot : 4.0.8
|
|
확인 방식 : 세 propertyName() 구현과 호출자·메서드 참조 대조, git 추적 파일 전체에서 발행된 이름 검색, 두 이름을 각각 참으로 둔 웹 컨텍스트 기동 두 구성, 이 설정 클래스의 메타데이터 재생성
|
|
소스 수정 : x
|
|
|
|
## 재현 조건
|
|
|
|
원문 근거는 분석 문서의 §35.3 표와 #L1279 절이다.
|
|
|
|
1. propertyName() 을 선언하는 세 자리를 찾아 구현을 나란히 읽는다.
|
|
2. 셋 각각을 부르는 자리를 센다. 메서드 참조도 함께 센다.
|
|
3. git 추적 파일 전체에서 발행된 이름을 찾고, 접두사까지만 잘라서도 찾는다. 운영자용 문서가 쓰는 이름도 함께 본다.
|
|
4. 실제 바인딩 접두사와 게이트 조건을 읽는다.
|
|
5. 출하 조립 루트가 이 리프의 설정을 어디에서 등록하는지 확인한다.
|
|
6. 게이트된 설정만 올린 구성과, 루트와 같은 스캔을 이 하위 패키지에 건 구성에서 두 이름을 각각 참으로 두고 컨텍스트를 띄운다.
|
|
7. 이 설정 클래스 한 파일에만 설정 프로세서를 걸어 어떤 이름이 실리는지 본다.
|
|
8. 그 타입을 만들고 등록하는 자리를 찾아 타입 자체가 배선되어 있는지 확인한다.
|
|
|
|
## 본문
|
|
|
|
<!-- body:start -->
|
|
|
|
자바독이 용도를 적고, 바로 아래에서 문자열을 그대로 돌려준다.
|
|
|
|
```java
|
|
// VirtualThreadProfile.java:74-77
|
|
/** The property that turns this on. */
|
|
public static String propertyName() {
|
|
return "backend.web.advanced.virtual-threads.enabled";
|
|
}
|
|
```
|
|
|
|
## 같은 메서드를 가진 형제 둘은 이름을 계산한다
|
|
|
|
:::evidence key="a14-f015-virtualthreadprofile-propertyname" alt="저장소 루트에서 돌린 정적 검색 출력 78줄. 선언 자리를 검색으로 센 결과 셋과 그 구현이 차례로 보이고, 앞의 둘은 상수 이름에서 문자열을 조립하며 셋째는 문자열을 그대로 적는다. 이어서 셋 각각을 부르는 자리가 메서드 참조까지 포함해 나오고, 발행된 이름 그대로 나오는 한 줄과 접두사까지만 잘라 찾았을 때 나오는 두 줄과 그 계획 문서가 접두사 다음 줄에 적은 name 항목, 운영자용 문서 두 편이 쓰는 이름, 실제 바인딩 접두사와 게이트 조건, 출하 조립 루트의 속성 스캔 애너테이션과 이 리프가 그 목록에 든 줄과 그 목록의 패키지 수, 그 타입을 만들고 빈으로 등록하는 자리, 루트가 거는 그 패키지 아래의 @ConfigurationProperties 타입 수와 그중 값을 요구하는 하나, 이 리프의 설정 프로세서 선언과 그것을 리프마다 check 에 거는 루트 빌드 블록이 보인다." caption="세 propertyName 과 그 이름이 나오는 자리 — 78줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
앞의 둘은 `name()` 을 소문자로 바꾸고 밑줄을 하이픈으로 바꿔 접두사에 붙인다. 상수를 새로 넣어도 이름이 상수와 어긋나지 않는다. 셋째는 그 조립을 하지 않는다.
|
|
|
|
호출은 앞의 둘에만 있다. `WebAdvancedReleaseTest` 의 `:48`·`:52` 와 `ResumeTokenTest` 의 `:187`·`:191` 이다. 각 쌍의 앞은 직접 호출이고 뒤는 메서드 참조다. 앞은 문자열을 그대로 적어 비교하고, 뒤는 그 열거형이 선언한 이름이 서로 겹치지 않는지만 센다. 웹 쪽은 열둘, 웹소켓 쪽은 열넷이다.
|
|
|
|
## 그 접두사는 코드 밖에 한 번 더 있다
|
|
|
|
이름 그대로 찾으면 자기 반환문 한 줄이다. 접두사까지만 잘라 찾으면 2026-08-13 확장 계획 문서가 한 줄 더 나온다.
|
|
|
|
```text
|
|
docs/web-superpowers-package/.../2026-08-13-web-advanced-capabilities-expansion-plan.md:195-202
|
|
|
|
Expected: FAIL because no virtual-thread MVC profile or admission guard exists.
|
|
|
|
- [ ] **Step 3: Implement the minimum production contract**
|
|
|
|
(199 의 java 펜스 한 줄 생략)
|
|
@Configuration
|
|
@ConditionalOnProperty(
|
|
prefix = "backend.web.advanced.virtual-threads",
|
|
```
|
|
|
|
계획은 이 설정 클래스가 아직 없다고 적은 뒤 이 접두사로 게이트하라고 지시한다. 다음 줄이 `name = "enabled"` 이므로 그 게이트가 읽는 속성은 이 메서드가 돌려주는 이름과 같다. 구현된 게이트는 다른 접두사를 쓴다.
|
|
|
|
```java
|
|
// VirtualThreadSettings.java:17 · VirtualThreadMvcConfiguration.java:32-35
|
|
@ConfigurationProperties(prefix = "backend.web.advanced.mvc-virtual-threads")
|
|
...
|
|
@ConditionalOnProperty(
|
|
prefix = "backend.web.advanced.mvc-virtual-threads",
|
|
name = "enabled",
|
|
havingValue = "true")
|
|
```
|
|
|
|
## 두 이름을 각각 두고 띄워 보면
|
|
|
|
:::evidence key="a14-f015-virtualthreadprofile-propertyname-bind" alt="JVM 프로브 출력 29줄. 메서드가 반환하는 이름과 실제 게이트 이름이 나란히 보인 뒤, 두 가지 구성의 결과가 표로 이어진다. 게이트된 설정만 올린 구성에서는 발행된 이름 쪽에 프로파일 빈도 설정 빈도 없고, 실제 이름 쪽에는 둘 다 있다. 루트가 이 리프에 거는 패키지를 그대로 스캔한 구성에서는 아무것도 채우지 않으면 보안 설정이 필수값을 요구해 기동이 멈추고, 그 한 값을 채운 세 행에서는 설정 빈이 모두 있고 enabled 값만 갈리며, 발행된 이름 행이 그 이름만 빼고 같게 둔 행과 같은 값을 낸다. 끝으로 고정 소스에서 새로 뽑은 설정 메타데이터의 이름 여섯과, 발행된 이름이 그 안에 없다는 확인이 보인다." caption="두 이름을 각각 참으로 둔 두 구성과 재생성한 설정 메타데이터 — 29줄 · exit 0" zoom="true"
|
|
:::
|
|
|
|
두 구성 모두 예산 세 값을 늘 함께 준다. 그 값이 없으면 실제 이름을 참으로 둔 행이 바인딩 검증에서 멈춘다.
|
|
|
|
첫 구성은 게이트된 설정 클래스만 올린다. 발행된 이름 쪽은 프로파일 빈도 설정 빈도 없다. 다만 그 설정 클래스가 이 구성에서 설정 빈을 등록하는 유일한 자리이므로, 설정 빈이 없는 것은 게이트가 닫힌 결과다.
|
|
|
|
출하 조립은 게이트와 무관하게 이 설정을 등록한다.
|
|
|
|
```java
|
|
// CaSkeletonApplication.java:58-80 중 세 줄
|
|
@ConfigurationPropertiesScan(
|
|
basePackages = {
|
|
...
|
|
"dev.caskeleton.adapter.inbound.web",
|
|
```
|
|
|
|
두 번째 구성은 그 애너테이션을 그 패키지 그대로 건다. 리프 전체를 걸면 `SecuritySettings` 가 `ca-skeleton.security.issuer-uri` 를 요구해 기동이 거기서 멈추므로, 그 한 값만 채우고 나머지는 두었다. 이 발견과 무관한 값이고, 리프의 여덟 설정 중 값을 요구하는 것은 그것 하나다. 그러면 설정 빈이 세 행 모두 있고 `enabled` 값만 갈린다. 발행된 이름을 참으로 둔 행은 그 이름을 빼고 같게 둔 `없음` 행과 똑같은 값을 낸다. 그 속성을 바인딩하겠다고 선언한 클래스가 없어서 기동을 막지도 않는다.
|
|
|
|
같은 프로브가 이 설정 클래스에 설정 프로세서를 돌려 메타데이터를 새로 뽑는다. 나오는 여섯은 모두 접두사 `backend.web.advanced.mvc-virtual-threads` 아래에 있고, 발행된 이름은 없다.
|
|
|
|
## 이름을 잘못 발행하는 타입은 살아 있다
|
|
|
|
```java
|
|
// VirtualThreadSettings.java:77-78 · VirtualThreadMvcConfiguration.java:39-41
|
|
public VirtualThreadProfile toProfile() {
|
|
return new VirtualThreadProfile(enabled, admissionLimit, databasePoolSize, outboundBulkhead);
|
|
@Bean
|
|
public VirtualThreadProfile virtualThreadProfile(VirtualThreadSettings properties) {
|
|
return properties.toProfile();
|
|
```
|
|
|
|
설정이 이 레코드를 만들고 설정 클래스가 빈으로 등록한다. `VirtualThreadSettings:91` 은 기동 검증에서 생성자를 다시 불러 예산이 맞는지 본다. 죽은 것은 타입이 아니라 거기 붙은 정적 메서드 하나다.
|
|
|
|
## 확인하지 못한 것
|
|
|
|
발행된 이름을 설정했을 때 기동 로그에 아무 말도 나오지 않는지는 확인하지 못했다. 프로브가 로깅을 끄고 돌았기 때문이다. 확인한 것은 기동이 실패하지 않는다는 것까지다.
|
|
|
|
두 번째 구성은 이 리프 하나만 스캔한다. 루트가 거는 스무 개 패키지를 모두 올린 것이 아니므로, 다른 리프의 설정이 이 결과에 영향을 주는지는 보지 않았다.
|
|
|
|
메타데이터를 다시 만들 때 이 설정 클래스 한 파일만 프로세서에 넣었다. 여기 나온 여섯은 이 클래스가 발행하는 이름이지 리프 전체 목록이 아니다.
|
|
|
|
계획 문서와 구현 중 어느 쪽 커밋이 먼저인지는 이력에서 보지 않았다. 확인한 것은 고정 리비전에 두 파일이 함께 있고 계획 쪽이 게이트 접두사로 이 이름을 적었다는 것까지다.
|
|
|
|
<!-- body:end -->
|