{ "assetKey": "a06-f013-recordapplied-probe", "kind": "terminal", "command": "set -e\nD=$(mktemp -d)\ntrap 'docker rm -f mg-rs >/dev/null 2>&1; rm -rf \"$D\"' EXIT\n\ndocker rm -f mg-rs >/dev/null 2>&1 || true\ndocker run -d --name mg-rs --network container:workmachine \\\n mongo:8.0.16 --port 57020 --bind_ip 127.0.0.1 --replSet rs0 >/dev/null\nfor i in $(seq 1 60); do\n docker exec mg-rs mongosh --port 57020 --quiet --eval 'db.runCommand({ping:1}).ok' 2>/dev/null \\\n | grep -q 1 && break\n sleep 1\ndone\ndocker exec mg-rs mongosh --port 57020 --quiet \\\n --eval 'rs.initiate({_id:\"rs0\",members:[{_id:0,host:\"127.0.0.1:57020\"}]})' >/dev/null 2>&1\nfor i in $(seq 1 60); do\n docker exec mg-rs mongosh --port 57020 --quiet --eval 'db.hello().isWritablePrimary' 2>/dev/null \\\n | grep -q true && break\n sleep 1\ndone\n\ncat > \"$D/LedgerFenceProbe.java\" <<'JAVA'\nimport com.mongodb.MongoWriteException;\nimport com.mongodb.client.MongoClients;\nimport com.mongodb.client.MongoDatabase;\nimport dev.caskeleton.adapter.outbound.mongo.migration.MongoCollectionMigrationLedger;\nimport dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationCheckpoint;\nimport dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationChecksum;\nimport dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationId;\nimport java.time.Instant;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.bson.Document;\n\npublic final class LedgerFenceProbe {\n\n private static final MongoMigrationId ID = new MongoMigrationId(\"20260829-001\");\n private static final Instant NOW = Instant.parse(\"2026-09-02T00:00:00Z\");\n private static final long LIVE = 5L;\n private static final long STALE = 1L;\n\n public static void main(String[] args) {\n MongoDatabase indexed = MongoClients.create(args[0]).getDatabase(\"indexed\");\n indexed.drop();\n MongoCollectionMigrationLedger ledger = new MongoCollectionMigrationLedger(indexed);\n ledger.ensureIndexes();\n\n System.out.println(\"[준비] 살아 있는 실행자가 펜스 \" + LIVE + \" 로 체크포인트를 쓴다\");\n ledger.saveCheckpoint(new MongoMigrationCheckpoint(ID, \"o-900\", 900L, NOW), LIVE);\n System.out.println(\" 체크포인트 : \" + one(indexed, \"mongoMigrationCheckpoint\"));\n System.out.println();\n\n System.out.println(\"[밀려난 실행자, 펜스 \" + STALE + \"] 같은 계약을 두 메서드에 건다\");\n attempt(\"saveCheckpoint\", () -> ledger.saveCheckpoint(\n new MongoMigrationCheckpoint(ID, \"o-1\", 1L, NOW), STALE));\n attempt(\"recordApplied \", () -> ledger.recordApplied(\n ID, new MongoMigrationChecksum(\"superseded\"), \"stale-runner\", NOW, STALE));\n System.out.println(\" 원장 항목 : \" + one(indexed, \"mongoMigrationLedger\"));\n System.out.println();\n\n System.out.println(\"[살아 있는 실행자, 펜스 \" + LIVE + \"] 밀려난 쪽이 쓴 뒤에 기록한다\");\n attempt(\"recordApplied \", () -> ledger.recordApplied(\n ID, new MongoMigrationChecksum(\"live\"), \"live-runner\", NOW, LIVE));\n System.out.println(\" 원장 항목 : \" + one(indexed, \"mongoMigrationLedger\"));\n System.out.println();\n\n System.out.println(\"[대조 1] 펜스가 미지정 값이면 두 메서드 다 거절한다\");\n attempt(\"saveCheckpoint\", () -> ledger.saveCheckpoint(\n new MongoMigrationCheckpoint(ID, \"o-0\", 0L, NOW), -1L));\n attempt(\"recordApplied \", () -> ledger.recordApplied(\n ID, new MongoMigrationChecksum(\"unfenced\"), \"runner\", NOW, -1L));\n System.out.println();\n\n MongoDatabase bare = MongoClients.create(args[0]).getDatabase(\"bare\");\n bare.drop();\n MongoCollectionMigrationLedger unindexed = new MongoCollectionMigrationLedger(bare);\n System.out.println(\"[대조 2] ensureIndexes 를 부르지 않은 원장에서 같은 경합\");\n attempt(\"recordApplied \", () -> unindexed.recordApplied(\n ID, new MongoMigrationChecksum(\"superseded\"), \"stale-runner\", NOW, STALE));\n attempt(\"recordApplied \", () -> unindexed.recordApplied(\n ID, new MongoMigrationChecksum(\"live\"), \"live-runner\", NOW, LIVE));\n System.out.println(\" 원장 항목 수 : \" + bare.getCollection(\"mongoMigrationLedger\").countDocuments());\n for (Document row : bare.getCollection(\"mongoMigrationLedger\").find()) {\n row.remove(\"_id\");\n System.out.println(\" \" + row.toJson());\n }\n System.exit(0);\n }\n\n private static String one(MongoDatabase database, String collection) {\n Document found = database.getCollection(collection).find().first();\n if (found == null) {\n return \"(없음)\";\n }\n found.remove(\"_id\");\n return found.toJson();\n }\n\n private static void attempt(String what, Runnable call) {\n try {\n call.run();\n System.out.println(\" \" + what + \" -> 수용\");\n } catch (RuntimeException rejected) {\n System.out.println(\" \" + what + \" -> 거절, \" + describe(rejected));\n }\n }\n\n private static String describe(RuntimeException failure) {\n String name = failure.getClass().getSimpleName();\n if (failure instanceof MongoWriteException write) {\n Matcher index = Pattern.compile(\"index: (\\\\S+)\").matcher(write.getError().getMessage());\n return name + \" code=\" + write.getError().getCode()\n + (index.find() ? \" index=\" + index.group(1) : \"\");\n }\n String line = failure.getMessage() == null ? \"\" : failure.getMessage().split(\"\\n\")[0].split(\" \\\\[\")[0];\n return name + \": \" + (line.length() > 110 ? line.substring(0, 110) + \" …\" : line);\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/LedgerFenceProbe.java\"\njava -Dstdout.encoding=UTF-8 -cp \"$D:$CP\" LedgerFenceProbe \\\n 'mongodb://127.0.0.1:57020/probe?replicaSet=rs0' 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:39:34+00:00", "sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916", "raw": "evidence/raw/a06-f013-recordapplied-probe.txt", "svg": "evidence/rendered/a06-f013-recordapplied-probe.svg", "rawSha256": "60c3d725acf6daf97a6c4741f1e87034953c637f63fcb4359b303926dd86438b", "lines": 22, "redaction": "none — 임시 컨테이너 실행 출력, 자격증명 없음" }