feat: web, websocket 어댑터 추가 구현
This commit is contained in:
+286
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
|
||||
if (ROOT / "docs").exists():
|
||||
DESIGN = ROOT / "docs/superpowers/specs/2026-08-13-web-inbound-http-api-execution-platform-design.md"
|
||||
STABLE = ROOT / "docs/superpowers/plans/2026-08-13-web-inbound-http-api-execution-platform-implementation-plan.md"
|
||||
ADVANCED = ROOT / "docs/superpowers/plans/2026-08-13-web-advanced-capabilities-expansion-plan.md"
|
||||
RESEARCH = ROOT / "research/source-web-deep-research.md"
|
||||
else:
|
||||
DESIGN = ROOT / "web-inbound-http-api-execution-platform-design.md"
|
||||
STABLE = ROOT / "web-inbound-http-api-execution-platform-implementation-plan.md"
|
||||
ADVANCED = ROOT / "web-advanced-capabilities-expansion-plan.md"
|
||||
RESEARCH = ROOT / "붙여넣은 마크다운(1)(20260813-120656).md"
|
||||
|
||||
checks: list[tuple[str, bool, str]] = []
|
||||
|
||||
def check(name: str, condition: bool, detail: str = "") -> None:
|
||||
checks.append((name, bool(condition), detail))
|
||||
|
||||
def read(path: Path) -> str:
|
||||
check(f"file exists: {path.name}", path.exists(), str(path))
|
||||
return path.read_text(encoding="utf-8") if path.exists() else ""
|
||||
|
||||
design = read(DESIGN)
|
||||
stable = read(STABLE)
|
||||
advanced = read(ADVANCED)
|
||||
research = read(RESEARCH)
|
||||
|
||||
check("design line floor", len(design.splitlines()) >= 1000, str(len(design.splitlines())))
|
||||
check("stable plan line floor", len(stable.splitlines()) >= 3000, str(len(stable.splitlines())))
|
||||
check("advanced plan line floor", len(advanced.splitlines()) >= 900, str(len(advanced.splitlines())))
|
||||
check("research line floor", len(research.splitlines()) >= 1000, str(len(research.splitlines())))
|
||||
|
||||
for name, text in [
|
||||
("design", design),
|
||||
("stable", stable),
|
||||
("advanced", advanced),
|
||||
]:
|
||||
check(f"{name} markdown fence balanced", text.count("```") % 2 == 0, str(text.count("```")))
|
||||
for forbidden in ["TODO", "TBD", "FIXME", "implement later", "fill in details"]:
|
||||
check(f"{name} has no placeholder {forbidden}", forbidden not in text, forbidden)
|
||||
|
||||
def task_sections(text: str) -> list[tuple[int, str]]:
|
||||
matches = list(re.finditer(r"(?m)^### Task (\d+): .+$", text))
|
||||
result: list[tuple[int, str]] = []
|
||||
for index, match in enumerate(matches):
|
||||
start = match.start()
|
||||
end = matches[index + 1].start() if index + 1 < len(matches) else len(text)
|
||||
result.append((int(match.group(1)), text[start:end]))
|
||||
return result
|
||||
|
||||
stable_tasks = task_sections(stable)
|
||||
advanced_tasks = task_sections(advanced)
|
||||
|
||||
check("stable task count", len(stable_tasks) == 58, str(len(stable_tasks)))
|
||||
check("advanced task count", len(advanced_tasks) == 19, str(len(advanced_tasks)))
|
||||
check(
|
||||
"stable task numbering consecutive",
|
||||
[number for number, _ in stable_tasks] == list(range(1, 59)),
|
||||
str([number for number, _ in stable_tasks]),
|
||||
)
|
||||
check(
|
||||
"advanced task numbering consecutive",
|
||||
[number for number, _ in advanced_tasks] == list(range(1, 20)),
|
||||
str([number for number, _ in advanced_tasks]),
|
||||
)
|
||||
|
||||
required_markers = [
|
||||
"**Files:**",
|
||||
"**Interfaces:**",
|
||||
"**Implementation requirements:**",
|
||||
"**Step 1: Write the failing test**",
|
||||
"**Step 2: Run the focused test and verify the expected failure**",
|
||||
"**Step 3: Implement the minimum production contract**",
|
||||
"**Step 4: Run the task test and its module contract suite**",
|
||||
"**Step 5: Commit the independently reviewable change**",
|
||||
"git commit -m",
|
||||
]
|
||||
|
||||
for plan_name, sections in [("stable", stable_tasks), ("advanced", advanced_tasks)]:
|
||||
for number, section in sections:
|
||||
for marker in required_markers:
|
||||
check(
|
||||
f"{plan_name} task {number} contains {marker}",
|
||||
marker in section,
|
||||
marker,
|
||||
)
|
||||
check(
|
||||
f"{plan_name} task {number} has test path",
|
||||
"- Test: `" in section,
|
||||
"",
|
||||
)
|
||||
check(
|
||||
f"{plan_name} task {number} has exact run command",
|
||||
"Run: `" in section,
|
||||
"",
|
||||
)
|
||||
check(
|
||||
f"{plan_name} task {number} has expected failure",
|
||||
"Expected:" in section and "FAIL" in section,
|
||||
"",
|
||||
)
|
||||
check(
|
||||
f"{plan_name} task {number} has expected pass",
|
||||
"Expected: PASS" in section,
|
||||
"",
|
||||
)
|
||||
|
||||
def create_paths(text: str) -> list[str]:
|
||||
return re.findall(r"(?m)^- Create: `([^`]+)`$", text)
|
||||
|
||||
stable_creates = create_paths(stable)
|
||||
advanced_creates = create_paths(advanced)
|
||||
|
||||
check(
|
||||
"stable create paths unique",
|
||||
len(stable_creates) == len(set(stable_creates)),
|
||||
f"{len(stable_creates)} paths",
|
||||
)
|
||||
check(
|
||||
"advanced create paths unique",
|
||||
len(advanced_creates) == len(set(advanced_creates)),
|
||||
f"{len(advanced_creates)} paths",
|
||||
)
|
||||
check(
|
||||
"stable and advanced create paths do not collide",
|
||||
set(stable_creates).isdisjoint(set(advanced_creates)),
|
||||
str(set(stable_creates) & set(advanced_creates)),
|
||||
)
|
||||
|
||||
design_terms = [
|
||||
"W1",
|
||||
"W2",
|
||||
"W3",
|
||||
"W4",
|
||||
"APPLICATION_COMMITTED",
|
||||
"CLIENT_OBSERVATION_UNKNOWN",
|
||||
"RFC 9457",
|
||||
"OpenAPI 3.1.2",
|
||||
"If-Match",
|
||||
"Idempotency",
|
||||
"202 Accepted",
|
||||
"Tomcat",
|
||||
"Jetty",
|
||||
"Reactor Netty",
|
||||
"Nginx",
|
||||
"business mutation + authoritative evidence same DB transaction",
|
||||
"Redis",
|
||||
"Request Evidence",
|
||||
"Application Evidence",
|
||||
"Response Evidence",
|
||||
]
|
||||
for term in design_terms:
|
||||
check(f"design contains key term: {term}", term in design, term)
|
||||
|
||||
stable_terms = [
|
||||
"same-PostgreSQL-transaction",
|
||||
"Application Commit 후 HTTP Response 유실 Fault Test",
|
||||
"Redis Concurrent Gate와 Replay Cache Adapter",
|
||||
"실제 Nginx Trusted Proxy",
|
||||
"실제 Tomcat MVC HTTP 계약 Gate",
|
||||
"Jetty MVC 호환성 Gate",
|
||||
"실제 Reactor Netty WebFlux 계약 Gate",
|
||||
"OpenAPI 3.1.2 Snapshot",
|
||||
"OpenAPI Breaking Diff",
|
||||
"Durable Operation",
|
||||
"same-PostgreSQL-transaction",
|
||||
"DB commit evidence의 유일한 source가 아니다",
|
||||
"webStableCheck",
|
||||
]
|
||||
for term in stable_terms:
|
||||
check(f"stable plan contains key term: {term}", term in stable, term)
|
||||
|
||||
advanced_terms = [
|
||||
"Virtual Thread",
|
||||
"Controlled Blocking Bridge",
|
||||
"JSON Merge Patch RFC 7396",
|
||||
"JSON Patch RFC 6902",
|
||||
"MVC SSE",
|
||||
"WebFlux SSE",
|
||||
"NDJSON",
|
||||
"JSON Text Sequence",
|
||||
"Messaging-backed SSE Replay",
|
||||
"OpenAPI 3.2 Experimental",
|
||||
"RateLimit Draft",
|
||||
"10k",
|
||||
"rollback",
|
||||
]
|
||||
for term in advanced_terms:
|
||||
check(f"advanced plan contains key term: {term}", term in advanced, term)
|
||||
|
||||
# Stable/advanced dependency boundary.
|
||||
check(
|
||||
"stable module map excludes modules/web-advanced",
|
||||
"modules/web-advanced/" not in stable.split("## 1. Stable 파일·모듈 구조", 1)[1].split("## 2.", 1)[0],
|
||||
"",
|
||||
)
|
||||
check(
|
||||
"advanced plan requires stable completion",
|
||||
"Stable Task 1~58" in advanced,
|
||||
"",
|
||||
)
|
||||
|
||||
# Evidence and idempotency invariants.
|
||||
for text_name, text in [("design", design), ("stable", stable)]:
|
||||
check(
|
||||
f"{text_name} separates ETag and idempotency",
|
||||
"ETag/If-Match" in text and "Idempotency" in text,
|
||||
"",
|
||||
)
|
||||
check(
|
||||
f"{text_name} says Redis is not sole commit evidence",
|
||||
("sole DB commit evidence" in text)
|
||||
or ("유일한 source" in text)
|
||||
or ("유일한 Source" in text),
|
||||
"",
|
||||
)
|
||||
check(
|
||||
f"{text_name} includes commit-response-loss",
|
||||
("Response Loss" in text)
|
||||
or ("response-loss" in text)
|
||||
or ("response write 전 TCP reset" in text),
|
||||
"",
|
||||
)
|
||||
|
||||
# No unsupported architecture in design/plan.
|
||||
for name, text in [("design", design), ("stable", stable)]:
|
||||
check(
|
||||
f"{name} forbids controller transaction",
|
||||
"Controller transaction" in text or "Controller @Transactional" in text or "Controller 또는 HTTP adapter에 업무 `@Transactional`" in text,
|
||||
"",
|
||||
)
|
||||
check(
|
||||
f"{name} forbids entity/document wire types",
|
||||
"Entity/Document" in text or "Entity·Document" in text or "JPA Entity·MongoDB Document" in text,
|
||||
"",
|
||||
)
|
||||
check(
|
||||
f"{name} does not declare Idempotency-Key as final RFC",
|
||||
"IETF 표준" not in text or "금지" in text,
|
||||
"",
|
||||
)
|
||||
|
||||
# Research grounding.
|
||||
check(
|
||||
"design title matches research topic",
|
||||
"인바운드 HTTP API 실행 플랫폼" in design and "인바운드 HTTP API 실행 플랫폼" in research,
|
||||
"",
|
||||
)
|
||||
check(
|
||||
"research includes execution evidence chain",
|
||||
"HTTP_RECEIVED" in research and "CLIENT_OBSERVATION_UNKNOWN" in research,
|
||||
"",
|
||||
)
|
||||
check(
|
||||
"research includes actual server matrix",
|
||||
"MVC + Tomcat" in research and "WebFlux + Reactor Netty" in research,
|
||||
"",
|
||||
)
|
||||
|
||||
# Optional package integrity.
|
||||
manifest = ROOT / "MANIFEST.sha256"
|
||||
if manifest.exists():
|
||||
for line in manifest.read_text(encoding="utf-8").splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
digest, relative = line.split(" ", 1)
|
||||
target = ROOT / relative
|
||||
actual = hashlib.sha256(target.read_bytes()).hexdigest() if target.exists() else ""
|
||||
check(f"manifest: {relative}", actual == digest, actual)
|
||||
|
||||
passed = sum(1 for _, ok, _ in checks if ok)
|
||||
failed = [(name, detail) for name, ok, detail in checks if not ok]
|
||||
|
||||
print(f"checks={len(checks)} passed={passed} failed={len(failed)}")
|
||||
for name, detail in failed:
|
||||
print(f"FAIL: {name} :: {detail}")
|
||||
|
||||
sys.exit(1 if failed else 0)
|
||||
Reference in New Issue
Block a user