{ "assetKey": "a10-f006-requireidentifier-branch", "kind": "terminal", "command": "set -e\nD=$(mktemp -d); trap 'rm -rf \"$D\"' EXIT\nJAR=/shared/codebase/clean-architecture-backend-template/src/adapter/outbound/cache-redis/build/libs/cache-redis-0.0.1+21234e38cdb9.jar\njava -version 2>&1 | head -1\ncat > \"$D/IdentifierBranchProbe.java\" <<'JAVA'\nimport dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisKeyRules;\nimport java.util.Locale;\nimport java.util.Random;\nimport java.util.regex.Pattern;\n\npublic final class IdentifierBranchProbe {\n\n private static final Pattern IDENTIFIER =\n Pattern.compile(\"^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$\");\n private static final Pattern JSON_WEB_TOKEN =\n Pattern.compile(\"^[A-Za-z0-9_-]{8,}\\\\.[A-Za-z0-9_-]{8,}\\\\.[A-Za-z0-9_-]{8,}$\");\n private static final Pattern INTERNATIONAL_PHONE = Pattern.compile(\"^\\\\+\\\\d[\\\\d.~-]{7,}$\");\n\n private static final String[] GUARDS = {\"문자클래스\", \"메일\", \"JWT\", \"전화\", \"접두\"};\n\n /** Which guards a value satisfies, in source order. */\n private static boolean[] guards(String v) {\n String low = v.toLowerCase(Locale.ROOT);\n return new boolean[] {\n !IDENTIFIER.matcher(v).matches(),\n v.indexOf('@') >= 0,\n JSON_WEB_TOKEN.matcher(v).matches(),\n INTERNATIONAL_PHONE.matcher(v).matches(),\n low.startsWith(\"bearer\") || low.startsWith(\"eyj\"),\n };\n }\n\n /** The message the first satisfied guard would produce. */\n private static String predict(String v) {\n boolean[] hit = guards(v);\n if (hit[0]) {\n return \"identifier must be 1..128 characters of [A-Za-z0-9._~-] and must not contain a\"\n + \" key separator\";\n }\n if (hit[1]) {\n return \"identifier must not contain a mail address\";\n }\n if (hit[2]) {\n return \"identifier must not contain a JSON web token\";\n }\n if (hit[3]) {\n return \"identifier must not contain a phone number\";\n }\n if (hit[4]) {\n return \"identifier must not contain authentication material\";\n }\n return \"통과\";\n }\n\n private static String actual(String v) {\n try {\n RedisKeyRules.requireIdentifier(v);\n return \"통과\";\n } catch (IllegalArgumentException e) {\n return e.getMessage();\n }\n }\n\n private static void run(String label, String v) {\n boolean[] hit = guards(v);\n StringBuilder marks = new StringBuilder();\n for (int i = 0; i < GUARDS.length; i++) {\n marks.append(hit[i] ? GUARDS[i] : \" \".repeat(GUARDS[i].length())).append(' ');\n }\n System.out.printf(\"%-16s %-4d %s| %s%n\", label, v.length(), marks, actual(v));\n }\n\n public static void main(String[] args) {\n String jwtHead = \"eyJhbGciOiJIUzI1NiJ9\";\n String longJwt = jwtHead + \".\" + \"a\".repeat(120) + \".\" + \"b\".repeat(96);\n\n System.out.println(\"입력 길이 걸리는 검사 전부 | requireIdentifier 가 낸 메시지\");\n System.out.println(\"-\".repeat(122));\n run(\"test: 메일\", \"person@example.com\");\n run(\"test: 전화\", \"+821012345678\");\n run(\"test: eyj\", jwtHead);\n run(\"test: bearer\", \"bearer-abcdefabcdef\");\n run(\"test: JWT\", jwtHead + \".eyJzdWIiOiIxIn0.c2lnbmF0dXJlLXZhbHVl\");\n run(\"test: 129자\", \"a\".repeat(129));\n System.out.println();\n System.out.println(\"JWT 분기만 걸리는 값이 있는가\");\n run(\"합성 JWT 26자\", \"abcdefgh.abcdefgh.abcdefgh\");\n run(\"_로 시작\", \"_bcdefgh.abcdefgh.abcdefgh\");\n run(\"실제 크기 JWT\", longJwt);\n System.out.println();\n System.out.println(\"메일과 전화 분기만 걸리는 값이 있는가\");\n System.out.println(\" 메일 분기는 '@' 를 요구한다 -> IDENTIFIER 문자 클래스에 '@' 없음\");\n System.out.println(\" 전화 분기는 '+' 로 시작한다 -> IDENTIFIER 첫 문자는 [A-Za-z0-9]\");\n System.out.println();\n System.out.println(\"옮겨 적은 정규식이 실제와 같은지 무작위 입력으로 대조한다.\");\n selfCheck(200_000);\n }\n\n private static void selfCheck(int rounds) {\n String alphabet = \"abcdefghjrzABZ09._~-@+:{} eyj.\";\n Random random = new Random(20260902L);\n int agreed = 0;\n int[] firstGuard = new int[GUARDS.length + 1];\n for (int i = 0; i < rounds; i++) {\n int length = 1 + random.nextInt(200);\n StringBuilder b = new StringBuilder(length);\n for (int j = 0; j < length; j++) {\n b.append(alphabet.charAt(random.nextInt(alphabet.length())));\n }\n // Half the draws start from a token shape so the later guards are reachable.\n String value = (i % 2 == 0) ? b.toString() : seeded(random, b.toString());\n if (actual(value).equals(predict(value))) {\n agreed++;\n }\n boolean[] hit = guards(value);\n int landed = GUARDS.length;\n for (int g = 0; g < GUARDS.length; g++) {\n if (hit[g]) {\n landed = g;\n break;\n }\n }\n firstGuard[landed]++;\n }\n System.out.printf(\" %d / %d 일치%n\", agreed, rounds);\n for (int g = 0; g < GUARDS.length; g++) {\n System.out.printf(\" %-10s 에서 갈린 입력 %d%n\", GUARDS[g], firstGuard[g]);\n }\n System.out.printf(\" %-10s %d%n\", \"통과\", firstGuard[GUARDS.length]);\n }\n\n /** A value shaped like something the later guards could match. */\n private static String seeded(Random random, String tail) {\n String core = tail.replaceAll(\"[^A-Za-z0-9_-]\", \"a\");\n return switch (random.nextInt(4)) {\n case 0 -> \"eyj\" + core;\n case 1 -> \"bearer\" + core;\n case 2 -> \"+8210\" + core.replaceAll(\"\\\\D\", \"1\");\n default -> {\n String seg = (core + \"abcdefgh\").substring(0, 8);\n yield seg + \".\" + seg + \".\" + seg;\n }\n };\n }\n}\nJAVA\njavac -encoding UTF-8 -cp \"$JAR\" -d \"$D\" \"$D/IdentifierBranchProbe.java\"\njava -Dstdout.encoding=UTF-8 -cp \"$JAR:$D\" IdentifierBranchProbe\n", "cwd": "/shared/codebase/clean-architecture-backend-template", "exitCode": 0, "executedAt": "2026-09-02T12:55:51+00:00", "sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916", "raw": "evidence/raw/a10-f006-requireidentifier-branch.txt", "svg": "evidence/rendered/a10-f006-requireidentifier-branch.svg", "rawSha256": "6e0ca6756b46beb4d977b5f42cb98b840c675ddd63bf6cdb0fa05c683501d301", "lines": 27, "redaction": "none — 밀폐 탐침 실행 출력, 자격증명 없음" }