feat: 설계 문서 추가

This commit is contained in:
DongHyeonka
2026-08-13 21:09:52 +09:00
parent 0a6dd0e419
commit 3b5aee50e3
33 changed files with 45861 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
from __future__ import annotations
import re
import sys
from collections import Counter
from pathlib import Path
DESIGN = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("messaging-platform-design.md")
PLAN = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("messaging-platform-implementation-plan.md")
errors: list[str] = []
checks: list[str] = []
def require(condition: bool, message: str) -> None:
if condition:
checks.append(message)
else:
errors.append(message)
def balanced_fences(text: str) -> bool:
return len(re.findall(r"^```", text, re.MULTILINE)) % 2 == 0
def line_count(text: str) -> int:
return len(text.splitlines())
design = DESIGN.read_text(encoding="utf-8")
plan = PLAN.read_text(encoding="utf-8")
require(line_count(design) >= 2_000, "설계서가 2,000행 이상이다")
require(line_count(plan) >= 4_000, "구현 계획서가 4,000행 이상이다")
require(balanced_fences(design), "설계서 Markdown 코드 블록이 균형을 이룬다")
require(balanced_fences(plan), "계획서 Markdown 코드 블록이 균형을 이룬다")
placeholder_patterns = {
"unresolved todo marker": r"\bT[O]DO\b",
"unresolved tbd marker": r"\bT[B]D\b",
"unresolved fix marker": r"\bF[I]XME\b",
"placeholder ADR number": r"ADR-X{2,}",
"wildcard build path": r"modules/messaging/\*/build\.gradle\.kts",
"deferred implementation phrase": r"implement\s+later|fill\s+in\s+details|similar\s+to\s+Task",
}
for name, pattern in placeholder_patterns.items():
require(not re.search(pattern, design, re.IGNORECASE), f"설계서에 {name}가 없다")
require(not re.search(pattern, plan, re.IGNORECASE), f"계획서에 {name}가 없다")
required_design_terms = [
"M1 Typed Messaging API",
"M2 Advanced API",
"M3 Native Capability",
"M4 Admin Plane",
"PublishCompletion",
"AMBIGUOUS",
"MessageEnvelope",
"DeliveryGuarantee",
"OrderingScope",
"Retry Policy Engine",
"DLQ·Parking·Redrive",
"Kafka Stable Adapter",
"RabbitMQ Stable Adapter",
"Transactional Outbox",
"Inbox와 Idempotent Consumer",
"Claim Check",
"Pulsar Experimental Adapter",
"NATS JetStream Experimental Adapter",
"Spring Cloud Stream Bridge",
"Security",
"Observability",
"호환성 인증 매트릭스",
"비지원 범위",
"완료 정의",
]
for term in required_design_terms:
require(term in design, f"설계서가 필수 항목 '{term}'을 포함한다")
require("AT_MOST_ONCE,\n AT_LEAST_ONCE" in design, "공통 DeliveryGuarantee가 두 가지 보장만 선언한다")
delivery_match = re.search(r"public enum DeliveryGuarantee \{(?P<body>.*?)\n\}", design, re.DOTALL)
ordering_match = re.search(r"public enum OrderingScope \{(?P<body>.*?)\n\}", design, re.DOTALL)
require(delivery_match is not None and "EXACTLY_ONCE" not in delivery_match.group("body"), "공통 DeliveryGuarantee enum에 EXACTLY_ONCE를 선언하지 않는다")
require(ordering_match is not None and "GLOBAL" not in ordering_match.group("body"), "공통 OrderingScope enum에 GLOBAL을 선언하지 않는다")
require("DLQ broker confirmation 확인\n→ source settlement" in design, "DLQ confirm 후 source settlement 순서를 명시한다")
require("같은 `messageId`" in design, "retry와 reliability에서 동일 message ID를 유지한다")
# Plan task structure.
task_numbers = [int(value) for value in re.findall(r"^### Task (\d+):", plan, re.MULTILINE)]
require(task_numbers == list(range(1, 45)), "Task 번호가 1부터 44까지 연속이다")
for task_number in task_numbers:
start = plan.index(f"### Task {task_number}:")
end = (
plan.index(f"### Task {task_number + 1}:", start)
if task_number < 44
else plan.index("## 3. Plan Self-Review Checklist", start)
)
section = plan[start:end]
for required in (
"**Files:**",
"**Interfaces:**",
"Step 1",
"Step 2",
"Step 3",
"Step 4",
"Step 5",
"git commit -m",
):
require(required in section, f"Task {task_number}'{required}'을 포함한다")
create_paths = re.findall(r"^- Create: `([^`]+)`", plan, re.MULTILINE)
duplicates = [path for path, count in Counter(create_paths).items() if count > 1]
require(not duplicates, "중복된 Create 파일 경로가 없다")
require(all("*" not in path for path in create_paths), "Create 파일 경로에 wildcard가 없다")
required_plan_terms = [
"Kafka Producer Adapter와 Publish Evidence",
"Kafka Consumer Group, Partition Coordinator",
"Kafka Native Transaction Capability",
"Kafka Share Group Experimental Adapter",
"Rabbit Publisher Confirm·Return Evidence Adapter",
"Rabbit Consumer Manual ACK",
"Transactional Outbox Repository",
"Inbox Transactional Idempotent Consumer",
"Debezium Outbox Event Router",
"Pulsar Experimental Adapter",
"NATS JetStream Experimental Adapter",
"Spring Cloud Stream Optional Bridge",
"Global Backpressure",
"Cross-broker 장애·보안·Reliability Contract Suite",
"성능 인증, Compatibility Matrix",
"지원 문서, Runbook, ADR, Release Gate",
]
for term in required_plan_terms:
require(term in plan, f"계획서가 필수 작업 '{term}'을 포함한다")
require("messageId`를 유지" in plan or "message ID를 유지" in plan, "계획서가 message identity 보존을 명시한다")
require("source를 ACK하지 않는다" in plan or "source ACK하지 않는다" in plan, "계획서가 DLQ 실패 시 source ACK 금지를 명시한다")
require("producer, consumer, admin credential" in plan, "계획서가 credential 분리를 명시한다")
require("messagingStableChaos" in plan, "Stable chaos aggregate task가 계획에 존재한다")
require("messagingPerformance" in plan, "performance aggregate task가 계획에 존재한다")
require("messagingCompatibility" in plan, "compatibility aggregate task가 계획에 존재한다")
print("# Messaging Superpowers 문서 정적 검증")
print()
print(f"- 설계서: `{DESIGN}` — {line_count(design):,}행, {len(design.encode('utf-8')):,} bytes")
print(f"- 계획서: `{PLAN}` — {line_count(plan):,}행, {len(plan.encode('utf-8')):,} bytes")
print(f"- Task 수: {len(task_numbers)}")
print(f"- Create 경로 수: {len(create_paths)}")
print(f"- 검증 항목 수: {len(checks) + len(errors)}")
print()
if errors:
print("## 결과: FAIL")
print()
for error in errors:
print(f"- FAIL: {error}")
sys.exit(1)
print("## 결과: PASS")
print()
for check in checks:
print(f"- PASS: {check}")