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
@@ -0,0 +1,172 @@
|
||||
# 열거형이 세운 규칙과 상수
|
||||
/**
|
||||
* The web capabilities that are not part of Stable, each behind its own flag.
|
||||
*
|
||||
* <p>One flag per capability, not one for "advanced". They have nothing in common operationally:
|
||||
* virtual threads change how every request is scheduled, streaming changes how long a response
|
||||
* holds a connection, XML adds a parser with a decades-long history of entity-expansion attacks. A
|
||||
* single switch would make those one decision, and a deployment that wanted the first would be
|
||||
* given the third.
|
||||
*
|
||||
* <p>Every constant is off unless named.
|
||||
*/
|
||||
선언된 상수 수 : 12
|
||||
24: MVC_VIRTUAL_THREADS,
|
||||
27: WEBFLUX_BLOCKING_BRIDGE,
|
||||
30: JSON_MERGE_PATCH,
|
||||
33: JSON_PATCH,
|
||||
36: SSE,
|
||||
39: NDJSON,
|
||||
42: JSON_SEQUENCE,
|
||||
45: FUNCTIONAL_WEBFLUX,
|
||||
48: CBOR,
|
||||
51: XML,
|
||||
54: OPENAPI_32,
|
||||
57: RATELIMIT_DRAFT_HEADERS;
|
||||
각 상수가 발행하는 속성 이름 :
|
||||
/** The property that turns this on. */
|
||||
public String propertyName() {
|
||||
return "backend.web.advanced." + name().toLowerCase(Locale.ROOT).replace('_', '-') + ".enabled";
|
||||
}
|
||||
|
||||
|
||||
# main 에 그 접두사가 나오는 자리 전부. 만드는 쪽과 읽는 쪽이 섞여 있다
|
||||
VirtualThreadProfile.java:76 return "backend.web.advanced.virtual-threads.enabled";
|
||||
MvcStreamingExecutorConfiguration.java:37 prefix = "backend.web.advanced.ndjson",
|
||||
VirtualThreadSettings.java:10 * Settings bound from {@code backend.web.advanced.mvc-virtual-threads.*}.
|
||||
VirtualThreadSettings.java:17 @ConfigurationProperties(prefix = "backend.web.advanced.mvc-virtual-threads")
|
||||
VirtualThreadMvcConfiguration.java:33 prefix = "backend.web.advanced.mvc-virtual-threads",
|
||||
WebAdvancedFeature.java:61 return "backend.web.advanced." + name().toLowerCase(Locale.ROOT).replace('_', '-') + ".enabled";
|
||||
그중 게이트나 바인딩인 셋 :
|
||||
17:@ConfigurationProperties(prefix = "backend.web.advanced.mvc-virtual-threads")
|
||||
33: prefix = "backend.web.advanced.mvc-virtual-threads",
|
||||
34- name = "enabled",
|
||||
35- havingValue = "true")
|
||||
36-public class VirtualThreadMvcConfiguration {
|
||||
37: prefix = "backend.web.advanced.ndjson",
|
||||
38- name = "enabled",
|
||||
39- havingValue = "true")
|
||||
40-public class MvcStreamingExecutorConfiguration {
|
||||
이름을 만들기만 하는 둘 :
|
||||
public String propertyName() {
|
||||
return "backend.web.advanced." + name().toLowerCase(Locale.ROOT).replace('_', '-') + ".enabled";
|
||||
/** The property that turns this on. */
|
||||
public static String propertyName() {
|
||||
return "backend.web.advanced.virtual-threads.enabled";
|
||||
}
|
||||
|
||||
# 두 스위치를 켜면 각각 무엇이 생기는가
|
||||
* <p>Named {@code applicationTaskExecutor} because that is the bean Spring Boot's MVC async
|
||||
* support looks for. A differently named bean is created, is never used, and leaves the container
|
||||
* default in place — which is the failure mode where the whole capability is switched on and
|
||||
* nothing changes.
|
||||
*/
|
||||
@Bean("applicationTaskExecutor")
|
||||
@ConditionalOnMissingBean(name = "applicationTaskExecutor")
|
||||
그런데 그 이름을 출하 조립 경로가 이미 쥐고 있다 :
|
||||
16: * with a bounded pool; naming the bean {@value #EXECUTOR_BEAN_NAME} backs off Boot's auto-config
|
||||
19:@Configuration
|
||||
25: public static final String EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
|
||||
40: @Bean(name = EXECUTOR_BEAN_NAME)
|
||||
41: @Primary
|
||||
42: ThreadPoolTaskExecutor applicationTaskExecutor(
|
||||
52: executor.setRejectedExecutionHandler(new LoggingAbortPolicy(EXECUTOR_BEAN_NAME, metrics));
|
||||
57: EXECUTOR_BEAN_NAME, () -> executor.getThreadPoolExecutor().getQueue().size());
|
||||
컴포넌트 스캔 제외 정규식에 bootstrap.async 가 있는가 : 0
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
|
||||
@ConditionalOnProperty(
|
||||
prefix = "backend.web.advanced.ndjson",
|
||||
name = "enabled",
|
||||
havingValue = "true")
|
||||
public class MvcStreamingExecutorConfiguration {
|
||||
72: return new MvcStreamWriter(encoder, StreamFraming.NDJSON);
|
||||
84: return new MvcStreamWriter(encoder, StreamFraming.JSON_SEQUENCE);
|
||||
그 두 미디어 타입을 내는 main 라우트 : 0
|
||||
MvcStreamWriter 를 advanced 밖에서 부르는 main 코드 : 0
|
||||
|
||||
# 레인이 무엇을 고르고 그 안에서 무엇을 단언하는가
|
||||
tasks.register('webAdvancedTest', Test) {
|
||||
group = 'verification'
|
||||
description = 'Runs every web Advanced capability contract.'
|
||||
testClassesDirs = sourceSets.test.output.classesDirs
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
useJUnitPlatform {
|
||||
includeTags 'web-advanced'
|
||||
}
|
||||
failOnNoDiscoveredTests = true
|
||||
그 태그를 단 시험 클래스 수 : 22
|
||||
그 파일에서 속성을 참으로 두는 자리. 셋은 만들어지는 빈을, 둘은 기동 실패를 단언한다 :
|
||||
43: "backend.web.advanced.mvc-virtual-threads.enabled=true",
|
||||
72: .withPropertyValues("backend.web.advanced.ndjson.enabled=true")
|
||||
92: "backend.web.advanced.mvc-virtual-threads.enabled=true",
|
||||
117: "backend.web.advanced.mvc-virtual-threads.enabled=true",
|
||||
136: .withPropertyValues("backend.web.advanced.mvc-virtual-threads.enabled=true")
|
||||
그중 켠 뒤 빈을 확인하는 자리 :
|
||||
.withPropertyValues("backend.web.advanced.ndjson.enabled=true")
|
||||
.withUserConfiguration(MvcStreamingExecutorConfiguration.class)
|
||||
.run(
|
||||
context ->
|
||||
assertThat(context)
|
||||
.hasNotFailed()
|
||||
.hasSingleBean(WebStreamPolicy.class)
|
||||
.hasSingleBean(WebStreamRegistry.class)
|
||||
.hasSingleBean(StreamRecordEncoder.class)
|
||||
.hasBean("mvcNdjsonWriter")
|
||||
.hasBean("mvcJsonSequenceWriter"));
|
||||
|
||||
# 롤백 시험 일곱이 무엇을 단언하는가
|
||||
49: void nothingIsCreatedWithNoFlags() {
|
||||
67: void explicitFalseCreatesNothing() {
|
||||
86: void flagsReportStablePreservation() {
|
||||
103: void codecsOffLeaveOnlyJson() {
|
||||
119: void draftHeadersOffWriteNothing() {
|
||||
134: void functionalRoutingOffServesNothing() {
|
||||
146: void everyGateDemandsRollbackEvidence() {
|
||||
속성 상태를 다루는 둘 — 하나는 값을 두지 않고, 하나는 거짓으로 둔다 :
|
||||
void nothingIsCreatedWithNoFlags() {
|
||||
// No property values at all — the shape a Stable deployment actually has.
|
||||
runner
|
||||
.withUserConfiguration(
|
||||
.withPropertyValues(
|
||||
"backend.web.advanced.ndjson.enabled=false",
|
||||
"backend.web.advanced.mvc-virtual-threads.enabled=false")
|
||||
.withUserConfiguration(
|
||||
플래그 값을 쓰는 유일한 시험 전문 :
|
||||
void flagsReportStablePreservation() {
|
||||
assertThat(
|
||||
dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeatureFlags.none()
|
||||
.stableBehaviourPreserved())
|
||||
.isTrue();
|
||||
for (WebAdvancedFeature feature : WebAdvancedFeature.values()) {
|
||||
boolean preserved =
|
||||
dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeatureFlags.of(feature)
|
||||
.stableBehaviourPreserved();
|
||||
assertThat(preserved)
|
||||
.as("%s claims to leave unrelated requests alone", feature)
|
||||
.isEqualTo(!feature.affectsUnrelatedRequests());
|
||||
}
|
||||
}
|
||||
|
||||
44: public boolean stableBehaviourPreserved() {
|
||||
45- return enabled.stream().noneMatch(WebAdvancedFeature::affectsUnrelatedRequests);
|
||||
46- }
|
||||
|
||||
# 롤백 검증이 대상으로 삼는 값
|
||||
22: public static WebAdvancedFeatureFlags none() {
|
||||
27: public static WebAdvancedFeatureFlags of(WebAdvancedFeature... features) {
|
||||
그 타입을 부르는 자리 전부 :
|
||||
test · WebAdvancedReleaseTest.java:21 WebAdvancedFeatureFlags flags = WebAdvancedFeatureFlags.none();
|
||||
test · WebAdvancedReleaseTest.java:37 assertThat(WebAdvancedFeatureFlags.of(WebAdvancedFeature.CBOR).stableBehaviourPreserved())
|
||||
test · WebAdvancedReleaseTest.java:40 WebAdvancedFeatureFlags.of(WebAdvancedFeature.MVC_VIRTUAL_THREADS)
|
||||
test · WebAdvancedRollbackIT.java:88 dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeatureFlags.none()
|
||||
test · WebAdvancedRollbackIT.java:93 dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeatureFlags.of(feature)
|
||||
|
||||
# 그 값을 돌리는 레인과, 속성 이름을 단언하는 시험
|
||||
.github/workflows/web-advanced-nightly.yml:46: :adapter:inbound:web:webAdvancedTest
|
||||
.github/workflows/web-advanced-release.yml:53: :adapter:inbound:web:webAdvancedTest
|
||||
test · ResumeTokenTest.java:187 assertThat(WebSocketAdvancedCapability.RESUME.propertyName())
|
||||
main · WebSocketAdvancedCapability.java:75 public String propertyName() {
|
||||
test · WebAdvancedReleaseTest.java:48 assertThat(WebAdvancedFeature.JSON_MERGE_PATCH.propertyName())
|
||||
main · VirtualThreadProfile.java:75 public static String propertyName() {
|
||||
main · WebAdvancedFeature.java:60 public String propertyName() {
|
||||
Reference in New Issue
Block a user