Files
document-haness/docs/clean-architecture-backend-template/final/evidence/meta/a06-f011-mongoregexpolicy-forbidden-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
7.0 KiB
JSON

{
"assetKey": "a06-f011-mongoregexpolicy-forbidden-probe",
"kind": "terminal",
"command": "set -e\nD=$(mktemp -d)\ntrap 'docker rm -f rx-standalone >/dev/null 2>&1; rm -rf \"$D\"' EXIT\n\ndocker rm -f rx-standalone >/dev/null 2>&1 || true\ndocker run -d --name rx-standalone --network container:workmachine \\\n mongo:8.0.16 --port 57018 --bind_ip 127.0.0.1 >/dev/null\nfor i in $(seq 1 60); do\n docker exec rx-standalone mongosh --port 57018 --quiet \\\n --eval 'db.runCommand({ping:1}).ok' 2>/dev/null | grep -q 1 && break\n sleep 1\ndone\n\ncat > \"$D/RegexPolicyProbe.java\" <<'JAVA'\nimport dev.caskeleton.adapter.outbound.mongo.query.MongoFieldDescriptor;\nimport dev.caskeleton.adapter.outbound.mongo.query.MongoOperator;\nimport dev.caskeleton.adapter.outbound.mongo.query.MongoQueryPolicy;\nimport dev.caskeleton.adapter.outbound.mongo.query.MongoRegexPolicy;\nimport dev.caskeleton.adapter.outbound.mongo.query.PolicyAwareMongoQueryBuilder;\nimport dev.caskeleton.adapter.outbound.mongo.query.budget.MongoOperationBudget;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Set;\nimport org.bson.Document;\nimport org.springframework.data.mongodb.core.MongoTemplate;\nimport org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;\nimport org.springframework.data.mongodb.core.query.Query;\n\npublic final class RegexPolicyProbe {\n\n public static void main(String[] args) {\n MongoRegexPolicy forbidden = MongoRegexPolicy.forbidden();\n System.out.println(\"forbidden() : \" + forbidden);\n System.out.println(\"standard() : \" + MongoRegexPolicy.standard());\n try {\n new MongoRegexPolicy(0, Set.of(), true);\n } catch (RuntimeException rejected) {\n System.out.println(\"길이 0 정책 : \" + rejected.getClass().getSimpleName()\n + \": \" + rejected.getMessage());\n }\n System.out.println();\n\n System.out.println(\"[검증] 금지 정책이 받는 조합과 막는 조합\");\n check(forbidden, \"^\", \"\");\n check(forbidden, \"^\", \"i\");\n check(forbidden, \"\", \"\");\n check(forbidden, \"a\", \"\");\n check(forbidden, \"^a\", \"\");\n check(forbidden, MongoRegexPolicy.literalPrefix(\"\"), \"\");\n check(forbidden, MongoRegexPolicy.escapedContains(\"\"), \"\");\n System.out.println();\n System.out.println(\"[도우미] 길이 검사를 얹은 인스턴스 메서드는 입력과 무관하게 던진다\");\n for (String literal : List.of(\"\", \"ab\")) {\n System.out.println(\" literalPrefix(\\\"\" + literal + \"\\\") = \"\n + MongoRegexPolicy.literalPrefix(literal)\n + \" (\" + MongoRegexPolicy.literalPrefix(literal).length() + \"자)\");\n try {\n forbidden.prefixPattern(literal);\n System.out.println(\" prefixPattern(\\\"\" + literal + \"\\\") -> 수용\");\n } catch (RuntimeException rejected) {\n System.out.println(\" prefixPattern(\\\"\" + literal + \"\\\") -> 거절, \"\n + rejected.getMessage().split(\" \\\\[\")[0]);\n }\n }\n System.out.println(\" escapedContains(\\\"\\\") = \" + MongoRegexPolicy.escapedContains(\"\")\n + \" (\" + MongoRegexPolicy.escapedContains(\"\").length() + \"자)\");\n System.out.println();\n\n MongoQueryPolicy policy =\n MongoQueryPolicy.allowing(\n List.of(MongoFieldDescriptor.withOperators(\n \"name\", Set.of(MongoOperator.EQ, MongoOperator.REGEX))))\n .withRegexPolicy(forbidden);\n System.out.println(\"[전제] 기본 연산자 집합에 REGEX 가 있는가 : \"\n + MongoOperator.DEFAULT_SET.contains(MongoOperator.REGEX)\n + \" \" + MongoOperator.DEFAULT_SET);\n Query query =\n new PolicyAwareMongoQueryBuilder(policy)\n .whereMatches(\"name\", \"^\", \"\")\n .build(MongoOperationBudget.standard());\n System.out.println(\"[조립] whereMatches(\\\"name\\\", \\\"^\\\", \\\"\\\") 가 만든 질의\");\n System.out.println(\" \" + query.getQueryObject().toJson());\n System.out.println();\n\n MongoTemplate template = new MongoTemplate(new SimpleMongoClientDatabaseFactory(args[0]));\n template.getCollection(\"people\").drop();\n List<Document> seeded = new ArrayList<>();\n seeded.add(new Document(\"name\", \"ana\"));\n seeded.add(new Document(\"name\", \"\"));\n seeded.add(new Document(\"name\", \"😀\"));\n seeded.add(new Document(\"name\", List.of(\"zz\")));\n seeded.add(new Document(\"name\", 12345));\n seeded.add(new Document(\"name\", true));\n seeded.add(new Document(\"name\", null));\n seeded.add(new Document(\"other\", \"x\"));\n seeded.forEach(template.getCollection(\"people\")::insertOne);\n List<Document> matched = template.find(query, Document.class, \"people\");\n System.out.println(\"[실행] 저장 \" + seeded.size() + \"건 · 매치 \" + matched.size() + \"건\");\n for (Document document : seeded) {\n document.remove(\"_id\");\n boolean hit = matched.stream().anyMatch(m -> m.get(\"name\") != null\n ? m.get(\"name\").equals(document.get(\"name\")) : false);\n System.out.println(\" \" + (hit ? \"매치 \" : \"미매치\") + \" \" + document.toJson());\n }\n template.getCollection(\"people\").drop();\n System.exit(0);\n }\n\n private static void check(MongoRegexPolicy policy, String pattern, String flags) {\n try {\n policy.validate(pattern, flags);\n System.out.println(\" (\\\"\" + pattern + \"\\\", \\\"\" + flags + \"\\\") -> 수용\");\n } catch (RuntimeException rejected) {\n System.out.println(\" (\\\"\" + pattern + \"\\\", \\\"\" + flags + \"\\\") -> 거절, \"\n + rejected.getMessage().split(\" \\\\[\")[0]);\n }\n }\n}\nJAVA\n\nCACHE=/root/.gradle/caches/modules-2/files-2.1\nLOCK=src/adapter/outbound/persistence-mongo/gradle.lockfile\nCP=src/adapter/outbound/persistence-mongo/build/classes/java/main\nwhile IFS= read -r line; do\n coord=${line%%=*}; confs=${line#*=}\n case \",$confs,\" in *\",testRuntimeClasspath,\"*) ;; *) continue ;; esac\n g=${coord%%:*}; r=${coord#*:}; n=${r%%:*}; v=${r##*:}\n jar=$(find \"$CACHE/$g/$n/$v\" -name '*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1)\n [ -n \"$jar\" ] && CP=\"$CP:$jar\"\ndone < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' \"$LOCK\")\n\njavac -encoding UTF-8 -cp \"$CP\" -d \"$D\" \"$D/RegexPolicyProbe.java\"\njava -Dstdout.encoding=UTF-8 -cp \"$D:$CP\" RegexPolicyProbe mongodb://127.0.0.1:57018/probe 2>&1 \\\n | grep -vE '^[0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} ' ",
"cwd": "/shared/codebase/clean-architecture-backend-template",
"exitCode": 0,
"executedAt": "2026-09-02T07:15:58+00:00",
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
"raw": "evidence/raw/a06-f011-mongoregexpolicy-forbidden-probe.txt",
"svg": "evidence/rendered/a06-f011-mongoregexpolicy-forbidden-probe.svg",
"rawSha256": "dcadd55442bc53eba86cc3353de313823e5e2fe755e994036f8035479f682d07",
"lines": 33,
"redaction": "none — 임시 컨테이너 실행 출력, 자격증명 없음"
}