docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
+145
@@ -0,0 +1,145 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: a17-f002-stomp
|
||||
title: 연결 티켓·origin 정책·메시지 권한·연결 예산이 요청 경로 밖이고, 그중 일부는 STOMP 어댑터가 다른 방식으로 대체한다
|
||||
topic: websocket-and-realtime
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:a17-f002-stomp
|
||||
evidenceCapturedOn: 2026-09-04
|
||||
body: case-a17-f002-stomp.body.md
|
||||
assets:
|
||||
- key: a17-f002-stomp
|
||||
file: ../../../final/evidence/rendered/a17-f002-stomp.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/a17-f002-stomp.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/17-adapter-inbound-websocket.md#L273 이다. 등록 지점과 세 번째 인터셉터, 출하 그래프 부재, 정책 열 파일의 계수와 생성 지점, 그리고 22 의 출처는 위 정적 검색에서 확인할 수 있다.
|
||||
---
|
||||
|
||||
# 연결 티켓·origin 정책·메시지 권한·연결 예산이 요청 경로 밖이고, 그중 일부는 STOMP 어댑터가 다른 방식으로 대체한다
|
||||
|
||||
`WebSocketConfig` 가 `AuthenticatedHandshakeInterceptor` 와 `WebSocketInboundAuthorizationInterceptor` 를 등록한다. `security`·`authz`·`idempotency`·`budget` 네 패키지의 정책 타입 열 개는 `stomp` 패키지가 한 번도 참조하지 않는다. 다만 이 모듈 자체가 두 출하 런타임 그래프에 모두 없어서, 어느 쪽도 지금 도는 코드는 아니다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **프레임워크 자유 신원 모델과 교차 테넌트 가드가 프로덕션에서 한 번도 참조되지 않는다**
|
||||
둘 다 보안 정책 타입 묶음의 프로덕션 참조가 0 이고, 실제로 도는 검사는 프레임워크 설정 쪽에 따로 있다.
|
||||
- **능력 프로퍼티 이름을 만드는 코드와 실제 게이트가 다른 접두사를 쓴다**
|
||||
같은 리프에서, 선언된 이름이나 타입을 읽는 코드가 실제 등록 경로에 없다. 저쪽은 능력 이름을 읽는 조건이 없고, 여기는 정책 열 타입을 참조하는 코드가 `stomp` 에 없다.
|
||||
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
|
||||
인바운드 권한을 정하는 구현이 둘인데, `WebSocketConfig` 가 등록하는 것은 `WebSocketInboundAuthorizationInterceptor` 뿐이고 `MessageAuthorizationPolicy` 는 `validate` 에 실려 가지도 않는다.
|
||||
|
||||
## 문제
|
||||
|
||||
인바운드 보안을 정하는 코드가 이 리프에 두 벌 있다.
|
||||
|
||||
어느 쪽이 채널에 등록되는지, 등록되지 않는 쪽이 어디까지 소비되는지, 그리고 그 대비가 어느 배포에서 관측되는지 확인했다.
|
||||
|
||||
## 결론
|
||||
|
||||
어댑터가 등록하는 것은 인터셉터 둘이다.
|
||||
|
||||
핸드셰이크에 AuthenticatedHandshakeInterceptor(34줄)가 WebSocketConfig:53 에서, 클라이언트 인바운드 채널에 WebSocketInboundAuthorizationInterceptor(53줄)가 :59 에서 붙는다. 그 설정 클래스는 ca-skeleton.websocket.enabled 조건 아래에 있다.
|
||||
|
||||
같은 채널에 붙는 인터셉터가 하나 더 있다. advanced/stomp/StompSecurityInterceptor(96줄)를 StompConfiguration:40 이 등록하고, 게이트는 app.websocket-platform.advanced.stomp.enabled 다.
|
||||
|
||||
정책 계층은 네 패키지에 열 파일이다. security 넷은 WebSocketOriginPolicy(116줄), WebSocketConnectionTicket(77줄), WebSocketAuthenticationProfile(57줄), WebSocketTicketStore(32줄), authz 하나는 MessageAuthorizationPolicy(93줄), budget 하나는 WebSocketConnectionBudget(103줄), idempotency 넷은 CommandReconciliation(84줄)·CommittedResultLedger(61줄)·WebSocketCommandKey(40줄)·WebSocketCommandOutcome(33줄)이다.
|
||||
|
||||
그 열 개 중 stomp 쪽이 이름이라도 언급하는 것은 하나도 없다. 검색 범위를 리프 전체로 넓히면 WebSocketOriginPolicy 와 WebSocketConnectionBudget 과 MessageAuthorizationPolicy 만 각각 한 자리에서 만들어진다.
|
||||
|
||||
MessageAuthorizationPolicy 는 한 단 더 간다. 그것을 받는 main 코드는 WebSocketPlatformStartupValidator.validate 의 파라미터 하나인데, 그 검증기의 생성자는 불리언 하나를 받고 생성 지점은 자기 시험 두 줄뿐이다. 이 정책을 validate 에 실어 보내는 코드가 없다.
|
||||
|
||||
노출로 볼 근거는 없다. 다만 근거는 문서 정합이 아니라 배선이다. app-bootstrap/build.gradle:196~:200 과 리프 CLAUDE.md:21~:23 이 이 모듈은 두 출하 런타임 그래프에 모두 없다고 적는다. 넓은 쪽만 꺼져 있는 것이 아니라 좁은 쪽도 지금은 돌지 않는다.
|
||||
|
||||
기록하는 것은 한 리프에 인바운드 보안 코드가 두 벌 있고, stomp 가 정책 열 타입을 한 번도 참조하지 않으며, 그중 MessageAuthorizationPolicy 는 넘겨지는 자리조차 없다는 것이다.
|
||||
|
||||
판정은 P2 이고 원문과 같다. 다만 원문 §13.1 이 정책 계층을 22 파일로 적은 것과 달리 실제로 센 것은 10 파일이고, 22 는 sub-scope 04 여덟 패키지의 main 합계다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
확인 방식 : 어댑터와 advanced 쪽 인터셉터의 등록 지점과 게이트 확인, 출하 런타임 그래프 포함 여부 확인, 정책 네 패키지의 파일·선언 형태·줄 수 전수, 열 타입의 stomp 참조와 main 생성 지점 계수, MessageAuthorizationPolicy 의 공개 멤버와 소비 사슬 추적, 원문이 적은 22 의 출처 대조, 리프 CLAUDE.md 의 범위와 인바운드 정책 절 확인
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. WebSocketConfig 에서 인터셉터를 만들고 등록하는 줄과 조건 애너테이션을 읽는다.
|
||||
2. advanced/stomp 에서 같은 채널에 등록되는 인터셉터와 그 게이트를 찾는다.
|
||||
3. app-bootstrap 빌드 파일과 리프 CLAUDE.md 에서 이 모듈의 출하 여부를 읽는다.
|
||||
4. security·authz·idempotency·budget 네 패키지의 파일을 이름·선언 형태·줄 수까지 전부 센다.
|
||||
5. 4에서 나열한 이름들을 stomp 쪽에서 되짚고, 같은 이름의 main 생성 지점도 함께 센다.
|
||||
6. MessageAuthorizationPolicy 의 공개 멤버를 나열하고, 그것을 받는 main 코드와 그 코드의 생성자 시그니처와 생성 지점을 차례로 확인한다.
|
||||
7. 원문이 22 로 적은 수가 어느 헤더에서 왔는지 찾고, 그 헤더가 나열한 패키지를 전부 세어 대조한다.
|
||||
8. 리프 CLAUDE.md 의 범위 절과 인바운드 정책 절을 끝까지 읽는다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
이 리프에는 인바운드 보안을 정하는 코드가 두 벌 있다. 어댑터가 등록하는 인터셉터들과, `security`·`authz`·`idempotency`·`budget` 네 패키지의 정책 타입들이다.
|
||||
|
||||
## 등록되는 인터셉터와 그 게이트
|
||||
|
||||
:::evidence key="a17-f002-stomp" alt="저장소 루트에서 돌린 정적 검색 출력 98줄. ca-skeleton.websocket 어댑터가 등록하는 인터셉터 둘의 줄 수와 WebSocketConfig 의 조건 애너테이션·필드 생성·addInterceptors·configureClientInboundChannel 줄이 먼저 나온다. 이어서 같은 채널에 붙는 세 번째 인터셉터 StompSecurityInterceptor 와 그것을 등록하는 StompConfiguration, 그리고 그쪽의 별도 프로퍼티 게이트가 나온다. 그다음 이 모듈이 두 출하 런타임 그래프에 모두 없다는 app-bootstrap 빌드 파일의 주석과 리프 CLAUDE.md 의 같은 서술이 이어진다. 정책 계층 열 파일이 패키지·이름·선언 형태·줄 수로 나열되고 합계가 10 으로 찍힌다. 그다음 열 타입 각각에 대해 stomp 참조 수와 main 생성 수와 같은 파일의 @Bean 유무가 표로 나오는데 stomp 참조는 전부 0 이고 main 생성은 셋만 1 이다. 이어서 원문이 22 로 적은 수가 sub-scope 04 여덟 패키지의 main 합계라는 것이 헤더와 계수로 확인되고, MessageAuthorizationPolicy 의 공개 메서드 여섯과 그것을 받는 자리, 그 받는 쪽의 생성자와 생성 지점이 나온다. 끝으로 리프 CLAUDE.md 의 범위 네 줄과 인바운드 정책 절 다섯 규칙이 보인다." caption="어댑터 인터셉터 둘과 세 번째 인터셉터 · 두 출하 그래프에 모두 없음 · 정책 열 파일과 stomp 참조 0 · 22 의 출처 · 검증기의 두 시그니처 · CLAUDE.md 범위 — 98줄 · exit 0" zoom="true"
|
||||
:::
|
||||
|
||||
`WebSocketConfig` 가 `AuthenticatedHandshakeInterceptor`(34줄)를 `:36`\~`:37` 에서 만들어 `:53` 의 `.addInterceptors` 로 핸드셰이크에 붙이고, `WebSocketInboundAuthorizationInterceptor`(53줄)를 `:44` 에서 만들어 `:59` 의 `configureClientInboundChannel` 에서 클라이언트 인바운드 채널에 등록한다. 이 설정 클래스는 `:32` 의 `@ConditionalOnProperty(prefix = "ca-skeleton.websocket", name = "enabled", havingValue = "true")` 아래에 있다.
|
||||
|
||||
인터셉터가 둘만은 아니다. `advanced/stomp/StompSecurityInterceptor`(96줄)가 `StompConfiguration:40` 에서 **같은 클라이언트 인바운드 채널에** 등록된다. 게이트는 다른 프로퍼티 접두사다 — `:24`\~`:27` 의 `app.websocket-platform.advanced.stomp.enabled`. 아래에서 어댑터 쪽 둘을 셀 때 이 세 번째는 포함하지 않는다.
|
||||
|
||||
## 어느 쪽도 출하 배포에서 돌지 않는다
|
||||
|
||||
`app-bootstrap/build.gradle:196`\~`:200` 이 이 리프를 `conditionalTransportTestImplementation` 으로만 물면서 주석에 적는다 — 이 프로젝트들은 main 의 `api`/`implementation`/`compileOnly`/`runtimeOnly` 에 없고 따라서 두 출하 런타임 그래프에서도 빠진다. 리프의 `CLAUDE.md:21`\~`:23` 이 같은 것을 다시 적는다. 등록되고 시험된다고 활성화되는 것이 아니며, 앞으로의 컴포지션이 의존을 의도적으로 추가하고 프로퍼티를 켜야 한다는 것이다.
|
||||
|
||||
따라서 아래의 대비는 이 모듈을 컴포지션에 넣고 프로퍼티를 켠 배포를 가정한 것이다.
|
||||
|
||||
## 정책 계층 열 파일
|
||||
|
||||
`security` 넷은 `WebSocketOriginPolicy`(final class, 116줄), `WebSocketConnectionTicket`(record, 77줄), `WebSocketAuthenticationProfile`(enum, 57줄), `WebSocketTicketStore`(interface, 32줄)다. `authz` 하나가 `MessageAuthorizationPolicy`(final class, 93줄), `budget` 하나가 `WebSocketConnectionBudget`(record, 103줄)이다. `idempotency` 넷 중 인터페이스가 둘(`CommandReconciliation` 84줄, `CommittedResultLedger` 61줄), 나머지가 `WebSocketCommandKey`(record, 40줄)와 `WebSocketCommandOutcome`(enum, 33줄)이다.
|
||||
|
||||
열 타입의 이름을 `stomp` 패키지 안에서 하나씩 찾으면 전부 0 건이다. 어댑터가 등록하는 두 인터셉터는 이 타입들을 알지 못한다.
|
||||
|
||||
다만 리프 전체로 넓히면 셋은 main 에서 생성된다. `WebSocketOriginPolicy`, `WebSocketConnectionBudget`, `MessageAuthorizationPolicy` 가 각각 한 자리씩이다. 그 자리들이 요청 경로에 오르는지까지는 이 검색이 답하지 않는다.
|
||||
|
||||
## MessageAuthorizationPolicy 를 넘기는 코드가 없다
|
||||
|
||||
`MessageAuthorizationPolicy` 의 공개 멤버는 여섯이다. `of`(\:39), `permits`(\:57), `declares`(\:70), `undeclaredAmong`(\:81), `requirements`(\:90), 그리고 클래스 자신이다.
|
||||
|
||||
이것을 자기 파일 밖에서 받는 main 코드는 `WebSocketPlatformStartupValidator` 하나다. 다만 생성자가 아니라 `validate(...)` 의 파라미터(`:56`)이고, 본문 `:106` 이 `undeclaredAmong` 을 부른다.
|
||||
|
||||
그 검증기의 생성자는 `:38` 의 `WebSocketPlatformStartupValidator(boolean productionProfile)` 다. 그것을 생성하는 두 줄은 `WebSocketPlatformStartupValidatorTest:40`·`:42` 이고 넘기는 인자는 불리언이다. 즉 이 정책 객체를 `validate` 에 실어 보내는 코드는 main 에도 test 에도 나오지 않았다.
|
||||
|
||||
## 등록된 인터셉터가 강제하는 것과 문서가 적은 것
|
||||
|
||||
리프 `CLAUDE.md:15`\~`:18` 이 이 모듈의 범위를 네 줄로 적고, 그중 `:16` 이 `"HTTP-handshake principal enforcement and client-inbound STOMP destination authorization"` 이다.
|
||||
|
||||
`:52`\~`:60` 의 인바운드 정책 절은 규칙 다섯이다. HTTP 업그레이드에 비어 있지 않은 `Principal` 이 이미 있어야 하고 어댑터가 자격을 인증하지는 않는다, `SUBSCRIBE` 는 설정된 브로드캐스트 목적지에만 허용한다, 인증된 `SEND` 는 `/app/**` 아래만 허용한다, `/topic/**` 로 가는 클라이언트 `SEND` 는 거부한다, 그리고 클라이언트가 보는 모든 처리 실패는 고정된 `WEBSOCKET_REQUEST_REJECTED` ERROR 코드가 된다.
|
||||
|
||||
앞의 넷이 두 인터셉터의 일이다. 다섯째는 `SafeStompSubProtocolErrorHandler` 가 맡는다.
|
||||
|
||||
## 원문과 갈리는 자리
|
||||
|
||||
원문 §13.1 은 플랫폼 정책 계층을 22 파일로 적었다. 22 는 같은 문서 `:235` 의 sub-scope 04 헤더가 적은 수다 — `security`·`authz`·`idempotency`·`budget`·`error`·`observability`·`admin`·`release` 여덟 패키지의 main 합계이고, 세어 보면 4+1+4+1+5+2+2+3 = 22 다. §12.1 이 정책 계층으로 한정해 열거한 것은 앞의 네 패키지 10 파일이다. §13.1 이 sub-scope 계수를 정책 계층 계수 자리에 옮겨 썼다.
|
||||
|
||||
원문 §12.2 는 `MessageAuthorizationPolicy` 의 등록을 "없음" 으로 적었다. 등록되지 않는다는 결론은 같지만, 그것을 `validate` 파라미터로 받는 main 코드가 하나 있고 그 검증기 자신이 다시 생성되지 않는다는 두 단은 그 표에 없다.
|
||||
|
||||
원문 §18.1 은 `StompConfiguration` 을 `@Bean` 0 이고 리프 타입 import 가 없어 프레임워크 설정만 조정하는 부류로 봤다. 같은 패키지라 import 문이 없을 뿐, 생성자로 `StompSecurityInterceptor` 를 받아 인바운드 채널에 등록한다.
|
||||
|
||||
그리고 원문 §13.1 의 "실제 배포에서 적용되는 보안은 `stomp` 패키지의 두 인터셉터" 는 이 리비전에서 성립하지 않는다. 그 두 인터셉터도 출하 런타임 그래프에 없다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
WebSocket 클라이언트를 붙여 구독과 전송을 시도해 보지 않았다. 확인한 것은 어느 코드가 등록되고 어느 이름이 어디서 참조·생성되는가까지다.
|
||||
|
||||
리프 밖 main 에서 세 타입을 생성하는 자리가 요청 경로에 오르는지는 추적하지 않았다. 나머지 일곱 타입의 리프 밖 참조도 세지 않았다.
|
||||
|
||||
제목이 말하는 "다른 방식으로 대체한다" 중 이 기록이 확인한 것은 목적지 허용 목록 하나다. 연결 티켓과 출처 정책과 연결 예산을 어댑터가 대신하는지는 보지 않았다.
|
||||
|
||||
WebSocket 클라이언트를 붙여 구독과 전송을 시도해 보지 않았다. 확인한 것은 어느 코드가 등록되고 어느 이름이 어디서 참조·생성되는가까지다.
|
||||
|
||||
리프 밖 main 에서 세 타입을 생성하는 자리가 요청 경로에 오르는지는 추적하지 않았다. 나머지 일곱 타입의 리프 밖 참조도 세지 않았다.
|
||||
|
||||
넓은 모델을 배선했을 때 좁은 모델과 어떤 충돌이 생기는지도 보지 않았다.
|
||||
|
||||
<!-- body:end -->
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: analysis-finding-a17-f003
|
||||
title: 능력 프로퍼티 이름을 만드는 코드와 실제 게이트가 다른 접두사를 쓴다
|
||||
topic: websocket-and-realtime
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:analysis-finding-a17-f003
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: analysis-finding-a17-f003
|
||||
file: ../../../final/evidence/rendered/analysis-finding-a17-f003.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/analysis-finding-a17-f003.txt
|
||||
source:
|
||||
- 원본 분석 절은 analysis/17-adapter-inbound-websocket.md#L426 이다.
|
||||
---
|
||||
|
||||
# 능력 프로퍼티 이름을 만드는 코드와 실제 게이트가 다른 접두사를 쓴다
|
||||
|
||||
능력 열거형의 속성 이름 메서드가 한 이름공간의 키를 만든다. 그 키를 읽는 조건 애너테이션이 없다. 실제로 능력을 켜는 키는 다른 이름공간에 있다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **속성 이름 메서드가 아무것도 게이트하지 않는 이름을 반환한다**
|
||||
다른 리프의 같은 형태다.
|
||||
- **연결 티켓과 origin 정책과 메시지 권한과 연결 예산이 요청 경로 밖이고 일부는 다른 방식으로 대체된다**
|
||||
같은 리프의 상위 사실이다.
|
||||
- **운영자가 문서를 보고 설정하면 켜지지 않는다**
|
||||
기록하는 이유다.
|
||||
|
||||
## 문제
|
||||
|
||||
고급 능력 열거형이 속성 이름을 계산하는 메서드를 가진다.
|
||||
|
||||
그 이름이 실제 게이트와 같은지 확인했다.
|
||||
|
||||
## 결론
|
||||
|
||||
다르다.
|
||||
|
||||
그 메서드가 반환하는 이름공간의 키를 읽는 조건 애너테이션이 없다.
|
||||
|
||||
운영자가 그 메서드가 알려 주는 키를 설정하면 아무 일도 일어나지 않는다.
|
||||
|
||||
실제로 능력을 켜는 키는 다른 접두를 쓰는 이름공간에 있다.
|
||||
|
||||
이것은 그 이름공간 전체가 소비자를 갖지 않는다는 상위 사실의 부분집합이다.
|
||||
|
||||
판정은 P3 다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
확인 방식 : 속성 이름 메서드 반환값과 조건 애너테이션 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 final/evidence/raw/185 계열에 있다.
|
||||
|
||||
1. 능력 열거형의 속성 이름 메서드를 읽는다.
|
||||
2. 그 이름공간의 키를 읽는 조건 애너테이션을 검색한다.
|
||||
3. 실제로 능력을 켜는 키를 확인한다.
|
||||
4. 두 이름공간의 접두를 대조한다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
`WebSocketAdvancedCapability.propertyName()`이 반환하는 `backend.websocket.advanced.*`를 읽는 `@ConditionalOnProperty`가 없다(§21.2).
|
||||
|
||||
## propertyName() 이 반환하는 접두사
|
||||
|
||||
:::evidence key="analysis-finding-a17-f003" alt="분석 문서 analysis/17-adapter-inbound-websocket.md 에서 이 기록의 근거 절을 그대로 잘라낸 15줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="analysis/17-adapter-inbound-websocket.md 발췌 — 15줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 운영자가 그 키를 설정하면 아무 일도 일어나지 않는다
|
||||
|
||||
실제로 능력을 켜는 키는 `app.websocket-platform.advanced.*`다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
그 키를 설정하고 띄워 아무 일도 일어나지 않는 것을 재현하지 않았다. 소비자 부재상 그 결과가 나온다.
|
||||
|
||||
<!-- body:end -->
|
||||
Reference in New Issue
Block a user