feat: 설계 문서 추가
This commit is contained in:
+164
@@ -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}")
|
||||
Reference in New Issue
Block a user