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>
15 lines
6.0 KiB
JSON
15 lines
6.0 KiB
JSON
{
|
|
"assetKey": "a05-f013-specificationpolicy-specification-unrestricted-probe",
|
|
"kind": "terminal",
|
|
"command": "mkdir -p /tmp/probe\n\necho '# 락파일이 고정한 좌표에서 클래스패스를 만든다. Gradle 실행 없이 재현된다.'\ncat > /tmp/probe/cp.sh <<'CPSH'\n# 락파일이 고정한 좌표에서 jar 경로를 만든다. Gradle 실행 없이 재현된다.\nCACHE=$(ls -d \"$HOME\"/.gradle/caches/modules-2/files-2.1 2>/dev/null || echo /root/.gradle/caches/modules-2/files-2.1)\njars=\"\"\nfor coord in \\\n org.springframework.data:spring-data-jpa \\\n org.springframework.data:spring-data-commons \\\n com.querydsl:querydsl-core \\\n com.querydsl:querydsl-jpa \\\n org.springframework:spring-core \\\n org.springframework:spring-tx \\\n jakarta.persistence:jakarta.persistence-api \\\n org.slf4j:slf4j-api ; do\n group=${coord%%:*}; name=${coord##*:}\n ver=$(grep -m1 \"^$group:$name:\" adapter/outbound/persistence-jpa/gradle.lockfile | cut -d: -f3 | cut -d= -f1)\n jar=$(find \"$CACHE/$group/$name/$ver\" -name \"$name-$ver*.jar\" ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1)\n echo \"$name $ver -> ${jar:-MISSING}\" >&2\n jars=\"$jars:$jar\"\ndone\necho \"adapter/outbound/persistence-jpa/build/classes/java/main$jars\"\nCPSH\nCP=$(bash /tmp/probe/cp.sh 2>/tmp/probe/cp.log)\nsed 's|/root/.gradle/caches/modules-2/files-2.1/|<cache>/|; s|/[0-9a-f]\\{40\\}/|/|' /tmp/probe/cp.log\necho -n '# app-bootstrap 의 락파일에 querydsl 이 있는가: '\ngrep -c querydsl app-bootstrap/gradle.lockfile || true\n\necho\necho '# 제한 없음 명세 팩토리가 어느 람다에 링크되는가'\njavap -p -v -cp \"$CP\" org.springframework.data.jpa.domain.Specification 2>/dev/null \\\n | awk '/^BootstrapMethods:/,0' | sed -n '1,6p' | cut -c1-110\necho '# 그 람다의 본문'\njavap -p -c -cp \"$CP\" org.springframework.data.jpa.domain.Specification 2>/dev/null \\\n | awk '/Predicate lambda\\$0\\(/{c=4} c-->0'\n\necho\necho '# 그 명세와 자바 널을 두 검사에 각각 넣는다'\ncat > /tmp/probe/SpecProbe.java <<'JAVA'\nimport dev.caskeleton.adapter.outbound.persistence.springdata.SpecificationPolicy;\nimport org.springframework.data.domain.PageRequest;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\n\n/** 라이브러리가 공식으로 주는 제한 없음 명세를 정책에 넘긴다. */\npublic class SpecProbe {\n public static void main(String[] args) {\n Pageable paged = PageRequest.of(0, 50);\n Specification<Object> unrestricted = Specification.unrestricted();\n\n System.out.println(\"Specification.unrestricted() 가 널인가 : \" + (unrestricted == null));\n System.out.println(\"그 명세의 toPredicate 가 돌려주는 값 : \" + unrestricted.toPredicate(null, null, null));\n\n System.out.println(\"requireBounded(토큰 없이) : \" + verdict(() -> SpecificationPolicy.requireBounded(unrestricted, paged, null)));\n System.out.println(\"requirePredicate : \" + verdict(() -> SpecificationPolicy.requirePredicate(unrestricted)));\n System.out.println(\"requireBounded(널 명세) : \" + verdict(() -> SpecificationPolicy.requireBounded(null, paged, null)));\n System.out.println(\"requirePredicate(널 명세) : \" + verdict(() -> SpecificationPolicy.requirePredicate(null)));\n }\n\n private static String verdict(Runnable call) {\n try {\n call.run();\n return \"통과\";\n } catch (RuntimeException refused) {\n return \"거절 — \" + refused.getMessage();\n }\n }\n}\nJAVA\njavac -encoding UTF-8 -cp \"$CP\" -d /tmp/probe /tmp/probe/SpecProbe.java \\\n && java -Dstdout.encoding=UTF-8 -cp \"$CP:/tmp/probe\" SpecProbe\n\necho\necho '# 페이지 경계 검사는 그대로 남아 있다'\nS=adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/SpecificationPolicy.java\ngrep -n -A3 'pageable.isUnpaged()' \"$S\"\necho '# 반면 requirePredicate 는 Pageable 을 받지 않는다'\ngrep -n 'public static void requirePredicate' \"$S\"\n\necho\necho '# 형제 정책에 Querydsl 쪽 대응물을 넣는다'\ncat > /tmp/probe/SiblingProbe.java <<'JAVA'\nimport com.querydsl.core.BooleanBuilder;\nimport dev.caskeleton.adapter.outbound.persistence.querydsl.PredicatePolicy;\nimport dev.caskeleton.adapter.outbound.persistence.querydsl.QueryPage;\n\npublic class SiblingProbe {\n public static void main(String[] args) {\n QueryPage page = QueryPage.of(0, 50);\n BooleanBuilder empty = new BooleanBuilder();\n System.out.println(\"빈 BooleanBuilder 가 널인가 : \" + (empty == null));\n System.out.println(\"hasValue : \" + empty.hasValue());\n try {\n PredicatePolicy.requireBounded(empty, page);\n System.out.println(\"PredicatePolicy 판정 : 통과\");\n } catch (RuntimeException refused) {\n System.out.println(\"PredicatePolicy 판정 : 거절 — \" + refused.getMessage());\n }\n }\n}\nJAVA\njavac -encoding UTF-8 -cp \"$CP\" -d /tmp/probe /tmp/probe/SiblingProbe.java \\\n && java -Dstdout.encoding=UTF-8 -cp \"$CP:/tmp/probe\" SiblingProbe\n\necho\necho '# 형제 쪽 진입점은 항상 limit 을 붙이고, 그 리프에서 Querydsl 은 compileOnly 다'\nQ=adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/querydsl/QuerydslJpaSupport.java\nsed -n '44,49p' \"$Q\"\ngrep -n 'compileOnly.*querydsl' adapter/outbound/persistence-jpa/build.gradle\nsed -n '13,15p' \"$Q\"",
|
|
"cwd": "/shared/codebase/clean-architecture-backend-template/src",
|
|
"exitCode": 0,
|
|
"executedAt": "2026-09-02T03:59:23+00:00",
|
|
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
|
|
"raw": "evidence/raw/a05-f013-specificationpolicy-specification-unrestricted-probe.txt",
|
|
"svg": "evidence/rendered/a05-f013-specificationpolicy-specification-unrestricted-probe.svg",
|
|
"rawSha256": "34881f95236b318f950d6b8fa0ad9b47489b01549fe8c74f9bb66197e983b0b0",
|
|
"lines": 56,
|
|
"redaction": "none — 코드베이스 정적 검색"
|
|
}
|