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>
70 lines
4.3 KiB
Plaintext
70 lines
4.3 KiB
Plaintext
# 검사기가 무엇을 하는지, 그리고 무엇을 위한 것인지
|
|
7:/**
|
|
8: * Prefixes each statement with the registered query name that issued it (design §37).
|
|
9: *
|
|
10: * <p>The comment travels with the SQL into {@code pg_stat_activity}, {@code auto_explain} output,
|
|
11: * and the slow-query log, which is the only place a DBA can connect a statement back to the use
|
|
12: * case that produced it. Without it, "which endpoint issues this query" is answered by grepping the
|
|
13: * codebase for fragments of SQL.
|
|
14: *
|
|
15: * <p>Only the registered name is emitted — never a parameter value, an id, or anything from the
|
|
16: * request. The name is already validated as bounded and low-cardinality, and this class
|
|
17: * additionally rejects the comment terminator so a name can never close the comment and continue as
|
|
18: * SQL.
|
|
20:public final class NamedStatementInspector implements StatementInspector {
|
|
21:
|
|
22: private static final long serialVersionUID = 1L;
|
|
23:
|
|
24: /**
|
|
25: * The comment terminator; a name containing it would end the comment and start statement text.
|
|
26: */
|
|
27: private static final String COMMENT_TERMINATOR = "*/";
|
|
28:
|
|
29: @Override
|
|
30: public String inspect(String sql) {
|
|
31: if (sql == null) {
|
|
32: return null;
|
|
33: }
|
|
34: Optional<QueryName> queryName = QueryNameContext.current();
|
|
35: if (queryName.isEmpty()) {
|
|
36: return sql;
|
|
37: }
|
|
38: String value = queryName.get().value();
|
|
39: if (value.contains(COMMENT_TERMINATOR)) {
|
|
40: return sql;
|
|
41: }
|
|
42: return "/* " + value + " */ " + sql;
|
|
43: }
|
|
# 이름 형식이 이미 * 와 / 를 막는다
|
|
16: private static final Pattern FORMAT = Pattern.compile("[a-z][a-z0-9.-]{2,95}");
|
|
|
|
# 하이버네이트가 이 구현을 설치하는 경로 — 저장소 루트, 확장자 무제한
|
|
# statement_inspector 설정 키: 0
|
|
# HibernatePropertiesCustomizer / SessionFactoryBuilder / ServiceRegistry / Integrator: 0
|
|
# persistence.xml: 0
|
|
# src 리소스와 자동설정 등록 파일의 FQCN: 0
|
|
# 그 클래스의 단위 테스트: 0
|
|
|
|
# 이름을 컨텍스트에 넣는 세 곳
|
|
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaStreamExecutor.java:63: return QueryNameContext.with(
|
|
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaKeysetQuerySupport.java:49: return QueryNameContext.with(
|
|
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaRepositoryFragmentSupport.java:72: return QueryNameContext.with(name, work);
|
|
# 그 컨텍스트를 읽는 곳: 1
|
|
|
|
# 이름을 SQL 로 옮기는 두 번째 경로
|
|
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/api/query/QueryName.java:8: * <p>The name is what appears in metrics, traces, and the {@code org.hibernate.comment} hint, so it
|
|
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaRepositoryFragmentSupport.java:27: private static final String COMMENT_HINT = "org.hibernate.comment";
|
|
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/querydsl/QuerydslJpaSupport.java:24: private static final String COMMENT_HINT = "org.hibernate.comment";
|
|
JpaRepositoryFragmentSupport.java:45: return entityManager.createQuery(jpql, resultType).setHint(COMMENT_HINT, name.value());
|
|
JpaRepositoryFragmentSupport.java:62: return entityManager.createNativeQuery(sql).setHint(COMMENT_HINT, name.value());
|
|
QuerydslJpaSupport.java:48: return query.offset(page.offset()).limit(page.size()).setHint(COMMENT_HINT, name.value());
|
|
# 그 힌트가 SQL 로 나오게 하는 use_sql_comments 를 켜는 곳: 0
|
|
|
|
# 이름을 넣는 세 클래스를 누가 쓰는가
|
|
# JpaStreamExecutor 그 타입을 쓰는 파일 전부: JpaStreamExecutorTest.java JpaStreamExecutor.java
|
|
# 상속(전체):
|
|
# JpaKeysetQuerySupport 그 타입을 쓰는 파일 전부: JpaKeysetQuerySupportTest.java JpaKeysetQuerySupport.java
|
|
# 상속(전체):
|
|
# JpaRepositoryFragmentSupport 그 타입을 쓰는 파일 전부: JpaRepositoryFragmentSupportTest.java JpaRepositoryFragmentSupport.java
|
|
# 상속(전체): JpaRepositoryFragmentSupportTest.java:74: private static final class Fragment extends JpaRepositoryFragmentSupport {
|