Files
document-haness/docs/clean-architecture-backend-template/final/evidence/meta/a05-f013-specificationpolicy-specification-unrestricted-probe.json
T
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

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 — 코드베이스 정적 검색"
}