Files
DongHyeonkaandClaude Opus 5 b2963105a8 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>
2026-09-04 22:51:59 +09:00

238 lines
18 KiB
Plaintext

# 카드 전체와 selected 카드의 태그 커버리지
카드 17 개 · implemented-candidate 6 · not-implemented 4 · selected 7
jpa-observability-lifecycle 태그 4 · 시나리오 2 · task-claim 0 · support-task 0 · 레지스트리가 덮지 않는 태그 ['no-skip']
jpa-security-baseline 태그 6 · 시나리오 3 · task-claim 1 · support-task 3 · 레지스트리가 덮지 않는 태그 ['no-skip']
jpa-flyway-migration 태그 4 · 시나리오 4 · task-claim 0 · support-task 0 · 레지스트리가 덮지 않는 태그 ['no-skip']
jpa-transaction-runtime 태그 4 · 시나리오 7 · task-claim 0 · support-task 0 · 레지스트리가 덮지 않는 태그 ['no-skip']
jpa-aggregate-store 태그 4 · 시나리오 1 · task-claim 0 · support-task 0 · 레지스트리가 덮지 않는 태그 ['no-skip']
jpa-query-model 태그 4 · 시나리오 1 · task-claim 0 · support-task 0 · 레지스트리가 덮지 않는 태그 ['no-skip']
jpa-primary-foundation 태그 4 · 시나리오 0 · task-claim 2 · support-task 5 · 레지스트리가 덮지 않는 태그 ['base-card-manifests', 'no-skip']
# 그 두 태그는 레지스트리가 덮을 수 없다
build.gradle 이 no-skip 을 필수로 요구하면서 클레임에서는 빼는 자리 :
build.gradle:1672 if (!requiredEvidence.contains('no-skip')) {
build.gradle:1673 violations << "${cardId}: required-evidence must include no-skip"
build.gradle:1674 }
build.gradle:1675 }
build.gradle:1676
build.gradle:1677 Object migrationNode = card.migration
build.gradle:1678 Set<String> allowedEvidenceClaims = requiredEvidence
build.gradle:1679 .findAll { String requirement -> requirement != 'no-skip' }
build.gradle:1680 .toSet()
생성기가 JUnit XML 결과로 채우는 자리 :
jpa-evidence.gradle:676 } as int
jpa-evidence.gradle:677 int errorCount = testResults.sum {
jpa-evidence.gradle:678 Map<String, Object> result -> result.errorCount as int
jpa-evidence.gradle:679 } as int
jpa-evidence.gradle:680 boolean noSkipResult = executedTestCount > 0 &&
jpa-evidence.gradle:681 skippedOrAbortedCount == 0 &&
jpa-evidence.gradle:682 failureCount == 0 &&
jpa-evidence.gradle:683 errorCount == 0
jpa-evidence.gradle:684 if (noSkipResult) {
jpa-evidence.gradle:685 covered << 'no-skip'
jpa-evidence.gradle:686 }
jpa-evidence.gradle:687 if (cardId == 'jpa-primary-foundation' &&
jpa-evidence.gradle:688 (card.prerequisites as List).every {
jpa-evidence.gradle:689 Object prerequisite -> manifestIds.containsKey(prerequisite as String)
jpa-evidence.gradle:690 }) {
jpa-evidence.gradle:691 covered << 'base-card-manifests'
jpa-evidence.gradle:692 }
# 시나리오가 가리키는 시험이 프로덕션을 지나는가
== jpa-aggregate-store
필수 태그 : ['real-postgresql', 'mapping', 'optimistic-conflict', 'no-skip']
시나리오 : roundTripsUuidAndInstantAndDetectsExpectedVersionConflict
덮는 태그 : ['real-postgresql', 'mapping', 'optimistic-conflict']
== jpa-query-model
필수 태그 : ['real-postgresql', 'query-contract', 'query-plan', 'no-skip']
시나리오 : boundedKeysetQueryUsesTheRepresentativeIndex
덮는 태그 : ['real-postgresql', 'query-contract', 'query-plan']
== jpa-observability-lifecycle
필수 태그 : ['real-postgresql', 'lifecycle', 'observability', 'no-skip']
시나리오 : startsPostgreSql16WithUtcAndProvidesAValidConnection
덮는 태그 : ['real-postgresql', 'lifecycle']
시나리오 : poolCapacityExhaustionAndShutdownAreBoundedAndObservable
덮는 태그 : ['observability']
# 그 세 시험의 본문
PostgreSqlAggregateIntegrationTest.java:19 @BeforeAll
PostgreSqlAggregateIntegrationTest.java:20 static void startPostgreSql() throws Exception {
PostgreSqlAggregateIntegrationTest.java:21 PostgreSqlReadinessSupport.assertDockerAvailable();
PostgreSqlAggregateIntegrationTest.java:22 postgres = PostgreSqlReadinessSupport.start();
PostgreSqlAggregateIntegrationTest.java:23 postgres.execute(
PostgreSqlAggregateIntegrationTest.java:24 "create table readiness_aggregate("
PostgreSqlAggregateIntegrationTest.java:25 + "id uuid primary key, title varchar(100) not null, "
PostgreSqlAggregateIntegrationTest.java:26 + "occurred_at timestamptz not null, version bigint not null)");
PostgreSqlAggregateIntegrationTest.java:27 }
PostgreSqlAggregateIntegrationTest.java:28
PostgreSqlAggregateIntegrationTest.java:29 @AfterAll
PostgreSqlAggregateIntegrationTest.java:30 static void stopPostgreSql() {
PostgreSqlAggregateIntegrationTest.java:36 @Test
PostgreSqlAggregateIntegrationTest.java:37 void roundTripsUuidAndInstantAndDetectsExpectedVersionConflict() throws Exception {
PostgreSqlAggregateIntegrationTest.java:38 UUID id = UUID.randomUUID();
PostgreSqlAggregateIntegrationTest.java:39 Instant occurredAt = Instant.parse("2026-07-28T12:00:00.123456Z");
PostgreSqlAggregateIntegrationTest.java:40 try (Connection connection = postgres.connection();
PostgreSqlAggregateIntegrationTest.java:41 PreparedStatement insert =
PostgreSqlAggregateIntegrationTest.java:42 connection.prepareStatement(
PostgreSqlAggregateIntegrationTest.java:43 "insert into readiness_aggregate(id,title,occurred_at,version) "
PostgreSqlAggregateIntegrationTest.java:44 + "values (?,?,?,0)")) {
PostgreSqlAggregateIntegrationTest.java:45 insert.setObject(1, id);
PostgreSqlAggregateIntegrationTest.java:46 insert.setString(2, "aggregate");
PostgreSqlAggregateIntegrationTest.java:47 insert.setObject(3, occurredAt.atOffset(ZoneOffset.UTC));
PostgreSqlAggregateIntegrationTest.java:48 assertThat(insert.executeUpdate()).isOne();
PostgreSqlAggregateIntegrationTest.java:49 }
PostgreSqlAggregateIntegrationTest.java:50
PostgreSqlAggregateIntegrationTest.java:51 try (Connection connection = postgres.connection();
PostgreSqlAggregateIntegrationTest.java:52 PreparedStatement query =
PostgreSqlAggregateIntegrationTest.java:53 connection.prepareStatement(
PostgreSqlAggregateIntegrationTest.java:54 "select id,title,occurred_at,version from readiness_aggregate where id=?")) {
PostgreSqlAggregateIntegrationTest.java:55 query.setObject(1, id);
PostgreSqlAggregateIntegrationTest.java:56 try (ResultSet row = query.executeQuery()) {
PostgreSqlAggregateIntegrationTest.java:57 assertThat(row.next()).isTrue();
PostgreSqlAggregateIntegrationTest.java:58 assertThat(row.getObject(1, UUID.class)).isEqualTo(id);
PostgreSqlAggregateIntegrationTest.java:59 assertThat(row.getString(2)).isEqualTo("aggregate");
PostgreSqlAggregateIntegrationTest.java:60 assertThat(row.getObject(3, java.time.OffsetDateTime.class).toInstant())
PostgreSqlAggregateIntegrationTest.java:61 .isEqualTo(occurredAt);
PostgreSqlAggregateIntegrationTest.java:62 assertThat(row.getLong(4)).isZero();
PostgreSqlAggregateIntegrationTest.java:63 }
PostgreSqlAggregateIntegrationTest.java:64 }
PostgreSqlAggregateIntegrationTest.java:65
PostgreSqlAggregateIntegrationTest.java:66 try (Connection first = postgres.connection();
PostgreSqlAggregateIntegrationTest.java:67 Connection second = postgres.connection();
PostgreSqlAggregateIntegrationTest.java:68 PreparedStatement firstUpdate =
PostgreSqlAggregateIntegrationTest.java:69 first.prepareStatement(
PostgreSqlAggregateIntegrationTest.java:70 "update readiness_aggregate set title=?,version=version+1 "
PostgreSqlAggregateIntegrationTest.java:71 + "where id=? and version=?");
PostgreSqlAggregateIntegrationTest.java:72 PreparedStatement secondUpdate =
PostgreSqlAggregateIntegrationTest.java:73 second.prepareStatement(
PostgreSqlAggregateIntegrationTest.java:74 "update readiness_aggregate set title=?,version=version+1 "
PostgreSqlAggregateIntegrationTest.java:75 + "where id=? and version=?")) {
PostgreSqlAggregateIntegrationTest.java:76 first.setAutoCommit(false);
PostgreSqlAggregateIntegrationTest.java:77 second.setAutoCommit(false);
PostgreSqlAggregateIntegrationTest.java:78 bindUpdate(firstUpdate, "first", id, 0);
PostgreSqlAggregateIntegrationTest.java:79 bindUpdate(secondUpdate, "second", id, 0);
PostgreSqlAggregateIntegrationTest.java:80 assertThat(firstUpdate.executeUpdate()).isOne();
PostgreSqlAggregateIntegrationTest.java:81 first.commit();
PostgreSqlAggregateIntegrationTest.java:82 assertThat(secondUpdate.executeUpdate()).isZero();
PostgreSqlAggregateIntegrationTest.java:83 second.rollback();
PostgreSqlAggregateIntegrationTest.java:84 }
PostgreSqlQueryIntegrationTest.java:22 @BeforeAll
PostgreSqlQueryIntegrationTest.java:23 static void startPostgreSql() throws Exception {
PostgreSqlQueryIntegrationTest.java:24 PostgreSqlReadinessSupport.assertDockerAvailable();
PostgreSqlQueryIntegrationTest.java:25 postgres = PostgreSqlReadinessSupport.start();
PostgreSqlQueryIntegrationTest.java:26 postgres.execute(
PostgreSqlQueryIntegrationTest.java:27 "create table readiness_query("
PostgreSqlQueryIntegrationTest.java:28 + "id uuid primary key, occurred_at timestamptz not null, payload text not null)");
PostgreSqlQueryIntegrationTest.java:29 postgres.execute("create index ix_readiness_query_keyset on readiness_query(occurred_at,id)");
PostgreSqlQueryIntegrationTest.java:30 postgres.execute(
PostgreSqlQueryIntegrationTest.java:31 "insert into readiness_query(id,occurred_at,payload) "
PostgreSqlQueryIntegrationTest.java:32 + "select gen_random_uuid(), "
PostgreSqlQueryIntegrationTest.java:33 + "timestamptz '2026-01-01T00:00:00Z' + (n || ' milliseconds')::interval, "
PostgreSqlQueryIntegrationTest.java:34 + "'payload-' || n from generate_series(1,1000) n");
PostgreSqlQueryIntegrationTest.java:35 postgres.execute("analyze readiness_query");
PostgreSqlQueryIntegrationTest.java:45 @Test
PostgreSqlQueryIntegrationTest.java:46 void boundedKeysetQueryUsesTheRepresentativeIndex() throws Exception {
PostgreSqlQueryIntegrationTest.java:47 OffsetDateTime cursorTime = OffsetDateTime.of(2026, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
PostgreSqlQueryIntegrationTest.java:48 UUID cursorId = new UUID(0, 0);
PostgreSqlQueryIntegrationTest.java:49 List<UUID> ids = new ArrayList<>();
PostgreSqlQueryIntegrationTest.java:50 String query =
PostgreSqlQueryIntegrationTest.java:51 "select id from readiness_query "
PostgreSqlQueryIntegrationTest.java:52 + "where (occurred_at,id) > (?,?) "
PostgreSqlQueryIntegrationTest.java:53 + "order by occurred_at,id limit ?";
PostgreSqlQueryIntegrationTest.java:54
PostgreSqlQueryIntegrationTest.java:55 try (Connection connection = postgres.connection();
PostgreSqlQueryIntegrationTest.java:56 PreparedStatement statement = connection.prepareStatement(query)) {
PostgreSqlQueryIntegrationTest.java:57 statement.setObject(1, cursorTime);
PostgreSqlQueryIntegrationTest.java:58 statement.setObject(2, cursorId);
PostgreSqlQueryIntegrationTest.java:59 statement.setInt(3, 25);
PostgreSqlQueryIntegrationTest.java:60 try (ResultSet rows = statement.executeQuery()) {
PostgreSqlQueryIntegrationTest.java:61 while (rows.next()) {
PostgreSqlQueryIntegrationTest.java:62 ids.add(rows.getObject(1, UUID.class));
PostgreSqlQueryIntegrationTest.java:63 }
PostgreSqlQueryIntegrationTest.java:64 }
PostgreSqlQueryIntegrationTest.java:65 }
PostgreSqlQueryIntegrationTest.java:66 assertThat(ids).hasSize(25).doesNotHaveDuplicates();
PostgreSqlQueryIntegrationTest.java:67
PostgreSqlQueryIntegrationTest.java:68 try (Connection connection = postgres.connection();
PostgreSqlQueryIntegrationTest.java:69 Statement setup = connection.createStatement()) {
PostgreSqlQueryIntegrationTest.java:70 setup.execute("set enable_seqscan=off");
PostgreSqlQueryIntegrationTest.java:71 try (PreparedStatement explain =
PostgreSqlQueryIntegrationTest.java:72 connection.prepareStatement("explain (format json) " + query)) {
PostgreSqlQueryIntegrationTest.java:73 explain.setObject(1, cursorTime);
PostgreSqlQueryIntegrationTest.java:74 explain.setObject(2, cursorId);
PostgreSqlQueryIntegrationTest.java:75 explain.setInt(3, 25);
PostgreSqlQueryIntegrationTest.java:76 try (ResultSet plan = explain.executeQuery()) {
PostgreSqlQueryIntegrationTest.java:77 assertThat(plan.next()).isTrue();
PostgreSqlQueryIntegrationTest.java:78 assertThat(plan.getString(1)).contains("ix_readiness_query_keyset");
PostgreSqlQueryIntegrationTest.java:79 }
PostgreSqlQueryIntegrationTest.java:80 }
PostgreSqlLifecycleIntegrationTest.java:51 @Test
PostgreSqlLifecycleIntegrationTest.java:52 @Timeout(10)
PostgreSqlLifecycleIntegrationTest.java:53 void poolCapacityExhaustionAndShutdownAreBoundedAndObservable() throws Exception {
PostgreSqlLifecycleIntegrationTest.java:54 PostgreSqlReadinessSupport bounded = PostgreSqlReadinessSupport.start(2, 300);
PostgreSqlLifecycleIntegrationTest.java:55 HikariDataSource dataSource = bounded.dataSource();
PostgreSqlLifecycleIntegrationTest.java:56 try {
PostgreSqlLifecycleIntegrationTest.java:57 try (Connection first = bounded.connection();
PostgreSqlLifecycleIntegrationTest.java:58 Connection second = bounded.connection()) {
PostgreSqlLifecycleIntegrationTest.java:59 PoolSnapshot saturated = snapshot(dataSource);
PostgreSqlLifecycleIntegrationTest.java:60 assertThat(saturated.state()).isEqualTo(PoolState.SATURATED);
PostgreSqlLifecycleIntegrationTest.java:61 assertThat(saturated.activeConnections()).isEqualTo(2);
PostgreSqlLifecycleIntegrationTest.java:62 assertThat(saturated.maximumConnections()).isEqualTo(2);
PostgreSqlLifecycleIntegrationTest.java:63 assertThat(saturated.boundedTags())
PostgreSqlLifecycleIntegrationTest.java:64 .containsExactlyInAnyOrderEntriesOf(
PostgreSqlLifecycleIntegrationTest.java:65 java.util.Map.of("component", "postgresql-primary", "state", "saturated"));
PostgreSqlLifecycleIntegrationTest.java:66
PostgreSqlLifecycleIntegrationTest.java:107 private record PoolSnapshot(
PostgreSqlLifecycleIntegrationTest.java:108 PoolState state,
PostgreSqlLifecycleIntegrationTest.java:109 int activeConnections,
PostgreSqlLifecycleIntegrationTest.java:110 int idleConnections,
PostgreSqlLifecycleIntegrationTest.java:111 int totalConnections,
PostgreSqlLifecycleIntegrationTest.java:112 int awaitingConnections,
PostgreSqlLifecycleIntegrationTest.java:113 int maximumConnections) {
PostgreSqlLifecycleIntegrationTest.java:114
PostgreSqlLifecycleIntegrationTest.java:115 private Map<String, String> boundedTags() {
PostgreSqlLifecycleIntegrationTest.java:116 return Map.of(
PostgreSqlLifecycleIntegrationTest.java:117 "component", "postgresql-primary", "state", state.name().toLowerCase(Locale.ROOT));
PostgreSqlLifecycleIntegrationTest.java:118 }
PostgreSqlLifecycleIntegrationTest.java:119 }
# 그 시험들이 프로덕션 타입에 닿는가
PostgreSqlAggregateIntegrationTest.java dev.caskeleton 를 import 하는 줄 : 0 개
PostgreSqlQueryIntegrationTest.java dev.caskeleton 를 import 하는 줄 : 0 개
PostgreSqlLifecycleIntegrationTest.java dev.caskeleton 를 import 하는 줄 : 0 개
[대조] 같은 소스 세트가 훑은 파일 :
27 개
import 만이 아니라 이름 단위로 훑어도 :
PostgreSqlAggregateIntegrationTest.java 프로덕션에 정의된 타입 이름 : 0 개
PostgreSqlQueryIntegrationTest.java 프로덕션에 정의된 타입 이름 : 0 개
PostgreSqlLifecycleIntegrationTest.java 프로덕션에 정의된 타입 이름 : 0 개
[대조] PostgreSqlTransactionIntegrationTest 에 같은 검사를 걸면 :
프로덕션에 정의된 타입 이름 : 15 개
두 시험이 쓰는 테이블 :
readiness_aggregate
readiness_query
그 이름이 프로덕션 소스나 마이그레이션에 나오는가 : 0 개
[대조] 그 검색이 훑은 main 파일 : 4714 개
postgresql-primary 가 프로덕션에 나오는 파일 : 0 개
# 대조 — 시나리오 일곱을 가진 카드
필수 태그 : ['real-postgresql', 'concurrency', 'fault', 'no-skip']
시나리오 : appliesTransactionLocalTimeoutsBeforeWorkAndResetsThemAfterCommit -> ['real-postgresql']
시나리오 : actionFailureProducesAConfirmedRollback -> ['fault']
시나리오 : serializableConflictIsRetriedOnlyByTheReplaySafePolicy -> ['concurrency']
시나리오 : deterministicDeadlockProducesExactlyOneTypedDeadlockFailure -> ['concurrency', 'fault']
시나리오 : lockAndStatementTimeoutsRollbackWithinTheConfiguredBounds -> ['fault']
시나리오 : poolExhaustionRejectsBeforeApplicationWorkStarts -> ['concurrency', 'fault']
시나리오 : connectionLossDuringCommitIsIndeterminateAndNeverBlindlyRetried -> ['fault']
PostgreSqlTransactionIntegrationTest.java 가 dev.caskeleton 를 import 하는 줄 : 15 개
:6 import dev.caskeleton.adapter.outbound.persistence.failure.PersistenceExceptionTranslator;
:7 import dev.caskeleton.adapter.outbound.persistence.failure.StandardSqlStateErrorMapping;
:8 import dev.caskeleton.adapter.outbound.persistence.postgresql.PostgreSqlLocalTimeoutConfigurer;
:9 import dev.caskeleton.adapter.outbound.persistence.postgresql.PostgreSqlSqlStateErrorMapping;
:10 import dev.caskeleton.adapter.outbound.persistence.transaction.JpaTransactionSettings;
:11 import dev.caskeleton.adapter.outbound.persistence.transaction.SpringTransactionPort;
:12 import dev.caskeleton.application.outbound.CallBudget;
:13 import dev.caskeleton.application.transaction.OperationId;