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>
90 lines
6.4 KiB
Plaintext
90 lines
6.4 KiB
Plaintext
# 이름 요구를 읽는 세 클래스의 줄 수
|
|
GraphQlOperationNamePolicy.java : 85 줄
|
|
GraphQlOperationSelectionHandler.java : 125 줄
|
|
GraphQlRequestEnvelopeValidator.java : 152 줄
|
|
|
|
# GraphQlOperationNamePolicy 를 자기 파일 밖에서 쓰는 자리
|
|
main · GraphQlOperationNameInterceptor.java:17 private final GraphQlOperationNamePolicy policy;
|
|
main · GraphQlOperationNameInterceptor.java:24 public GraphQlOperationNameInterceptor(GraphQlOperationNamePolicy policy) {
|
|
test · GraphQlOperationNamePolicyTest.java:14 class GraphQlOperationNamePolicyTest {
|
|
test · GraphQlOperationNamePolicyTest.java:18 var policy = GraphQlOperationNamePolicy.production();
|
|
test · GraphQlOperationNamePolicyTest.java:29 GraphQlOperationNamePolicy.local()
|
|
test · GraphQlOperationNamePolicyTest.java:40 GraphQlOperationNamePolicy.local()
|
|
test · GraphQlOperationNamePolicyTest.java:50 var policy = GraphQlOperationNamePolicy.production();
|
|
test · GraphQlOperationNamePolicyTest.java:68 var policy = GraphQlOperationNamePolicy.production();
|
|
test · GraphQlOperationNamePolicyTest.java:78 var policy = GraphQlOperationNamePolicy.production();
|
|
test · GraphQlOperationNamePolicyTest.java:94 var interceptor = new GraphQlOperationNameInterceptor(GraphQlOperationNamePolicy.production());
|
|
|
|
# GraphQlOperationNameInterceptor 를 자기 파일 밖에서 쓰는 자리
|
|
test · GraphQlOperationNamePolicyTest.java:94 var interceptor = new GraphQlOperationNameInterceptor(GraphQlOperationNamePolicy.production());
|
|
|
|
# GraphQlRequestEnvelopeValidator 를 실제로 만드는 자리 전부
|
|
test · GraphQlRequestBoundsTest.java:38
|
|
test · GraphQlRequestBoundsTest.java:50
|
|
test · GraphQlRequestEnvelopeValidatorTest.java:19
|
|
test · GraphQlRequestEnvelopeValidatorTest.java:30
|
|
test · GraphQlRequestEnvelopeValidatorTest.java:41
|
|
test · GraphQlRequestEnvelopeValidatorTest.java:77
|
|
test · GraphQlRequestEnvelopeValidatorTest.java:92
|
|
test · GraphQlRequestEnvelopeValidatorTest.java:101
|
|
testFixtures · GraphQlContractFixture.java:84
|
|
|
|
# namedOperationRequired 를 읽는 프로덕션 코드
|
|
main · GraphQlRequestEnvelopeValidator.java:63 policy.namedOperationRequired(),
|
|
main · GraphQlRequestEnvelopeValidator.java:134 if (namedOperationRequired && GraphQlOperationName.parse(envelope.operationName()).isEmpty()) {
|
|
main · GraphQlOperationSelectionHandler.java:74 if (policy.namedOperationRequired() && selected.getName() == null) {
|
|
|
|
# GraphQlClientPolicy 인스턴스를 만드는 프로덕션 코드 전부
|
|
main · GraphQlPlatformAutoConfiguration.java:303 return GraphQlClientPolicy.defaults(
|
|
main · GraphQlClientPolicy.java:85 public static GraphQlClientPolicy defaults(
|
|
main · GraphQlClientPolicy.java:87 return new GraphQlClientPolicy(
|
|
그 빈을 내놓는 자리 :
|
|
GraphQlPlatformAutoConfiguration.java:300 @Bean
|
|
GraphQlPlatformAutoConfiguration.java:301 @ConditionalOnMissingBean
|
|
GraphQlPlatformAutoConfiguration.java:302 public GraphQlClientPolicy graphQlClientPolicy(GraphQlPlatformSettings properties) {
|
|
GraphQlPlatformAutoConfiguration.java:303 return GraphQlClientPolicy.defaults(
|
|
GraphQlPlatformAutoConfiguration.java:304 properties.limits().maximumPageSize(),
|
|
GraphQlPlatformAutoConfiguration.java:305 properties.limits().maximumComplexity(),
|
|
GraphQlPlatformAutoConfiguration.java:306 properties.console().introspectionEnabled());
|
|
GraphQlPlatformAutoConfiguration.java:307 }
|
|
GraphQlPlatformAutoConfiguration.java:308
|
|
defaults 가 마지막 세 성분에 넣는 값과 그 성분의 이름 :
|
|
값 GraphQlClientPolicy.java:101 introspectionAllowed,
|
|
값 GraphQlClientPolicy.java:102 false,
|
|
값 GraphQlClientPolicy.java:103 false);
|
|
이름 GraphQlClientPolicy.java:49 boolean introspectionAllowed,
|
|
이름 GraphQlClientPolicy.java:50 boolean persistedOperationOnly,
|
|
이름 GraphQlClientPolicy.java:51 boolean namedOperationRequired) {
|
|
|
|
# 배선된 핸들러가 요청 경로에 오르는 사슬
|
|
main · GraphQlPlatformAutoConfiguration.java:403 return GraphQlExecutionChain.stable(
|
|
main · GraphQlPlatformAutoConfiguration.java:404 new GraphQlOperationSelectionHandler(clientPolicy),
|
|
main · GraphQlPlatformAutoConfiguration.java:420 return new GraphQlPlatformInstrumentation(chain);
|
|
main · GraphQlExecutionChain.java:43 public static GraphQlExecutionChain stable(
|
|
GraphQlPlatformInstrumentation.java:81 chain.run(
|
|
그 사슬이 붙는 어댑터 자체의 조건 :
|
|
GraphQlRootAutoConfiguration.java:4 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
GraphQlRootAutoConfiguration.java:21 @ConditionalOnProperty(prefix = "backend.graphql", name = "enabled", havingValue = "true")
|
|
|
|
# GraphQlRequestContext.operationId 를 읽는 프로덕션 코드
|
|
main 에서 0 건
|
|
참고 — 이 모듈 main 에서 operationId() 로 잡히는 둘은 별개 타입이다 :
|
|
main · GraphQlPersistedOperationAdminService.java:68 command.operationId(),
|
|
main · GraphQlPersistedOperationInterceptor.java:50 GraphQlPersistedOperation operation = lookup.require(request.operationId());
|
|
|
|
# 미배선 정책이 이름을 검사하는 방식과 GraphQlOperationName.parse 의 계약
|
|
GraphQlOperationNamePolicy.java:54 if (namedRequired && !selection.named()) {
|
|
GraphQlOperationNamePolicy.java:55 throw new GraphQlAnonymousOperationException("named operation required");
|
|
GraphQlOperationNamePolicy.java:56 }
|
|
GraphQlOperationNamePolicy.java:57 if (selection.named() && GraphQlOperationName.parse(selection.operationName()).isEmpty()) {
|
|
GraphQlOperationNamePolicy.java:58 // Validates the bounded naming pattern; an unbounded name would defeat the reason for
|
|
GraphQlOperationNamePolicy.java:59 // requiring one at all.
|
|
GraphQlOperationNamePolicy.java:60 throw new GraphQlAnonymousOperationException("named operation required");
|
|
GraphQlOperationNamePolicy.java:61 }
|
|
GraphQlOperationName.java:37 public static Optional<GraphQlOperationName> parse(String candidate) {
|
|
GraphQlOperationName.java:38 if (candidate == null || candidate.isBlank()) {
|
|
GraphQlOperationName.java:39 return Optional.empty();
|
|
GraphQlOperationName.java:40 }
|
|
GraphQlOperationName.java:41 return Optional.of(new GraphQlOperationName(candidate));
|
|
GraphQlOperationName.java:42 }
|