feat: web, websocket 어댑터 추가 구현

This commit is contained in:
DongHyeonka
2026-08-28 17:01:27 +09:00
parent 0137263441
commit a24ece9cf7
883 changed files with 100584 additions and 2623 deletions
@@ -0,0 +1,7 @@
f3e98b6e72a3a43c38799aac7a333387afa12d7d007dc6966e58a3d725c0f4b7 README.md
c04f805a3f05bee50cf3d62fc531dfc9a33664ee2621b5281b1f3c0ec0885dca VALIDATION.md
27c0ddd7ab0f390fe074f14d0cb14e815c60e7544eabe8aa7a2e4ae462f91cad docs/superpowers/plans/2026-08-13-web-advanced-capabilities-expansion-plan.md
c780fb635da38b7a1c2f2f73969e129c5ac72d1852133dcb29fc10701fa453c5 docs/superpowers/plans/2026-08-13-web-inbound-http-api-execution-platform-implementation-plan.md
7b82fd9850878a5f43828243ddec992b3fae0066d31eaa898b9e1e13528bace7 docs/superpowers/specs/2026-08-13-web-inbound-http-api-execution-platform-design.md
b8c9f6d7ac055653a425f62458ece3c68ee8d7dc48827c7c28266947fadb5daf research/source-web-deep-research.md
9af756a602f320abee5f40ad7dd71936644ee28aef30b442e72f85e0b8086d0f validate_web_docs.py
+73
View File
@@ -0,0 +1,73 @@
# Web Superpowers Package
Spring Boot 기반 **인바운드 HTTP API 실행 플랫폼 `web`**의 설계, Stable 구현 계획, Advanced 확장 계획, 원본 심층 리서치, 정적 검증 도구를 묶은 패키지입니다.
## 구성
```text
web-superpowers-package/
├── docs/
│ └── superpowers/
│ ├── specs/
│ │ └── 2026-08-13-web-inbound-http-api-execution-platform-design.md
│ └── plans/
│ ├── 2026-08-13-web-inbound-http-api-execution-platform-implementation-plan.md
│ └── 2026-08-13-web-advanced-capabilities-expansion-plan.md
├── research/
│ └── source-web-deep-research.md
├── README.md
├── VALIDATION.md
├── validate_web_docs.py
└── MANIFEST.sha256
```
## 문서 규모
| 항목 | 규모 |
|---|---:|
| 설계서 | 1,656행 |
| Stable 구현 계획 | 4,762행 / 58 Task |
| Advanced 계획 | 1,532행 / 19 Task |
| 원본 리서치 | 1,597행 |
## 핵심 구현 순서
```text
Stable Task 1~58
→ Stable Release Gate
→ Advanced Task 1~19
→ Advanced Promotion Gate
```
Stable 구현 완료 전 Advanced module을 적용하지 않습니다.
## 정적 검증
```bash
./validate_web_docs.py
sha256sum -c MANIFEST.sha256
```
## 실행 방식
구현 시 `superpowers:subagent-driven-development`를 권장합니다.
각 Task마다 다음 review를 분리합니다.
```text
1. Specification review
2. Code quality·test evidence review
```
## 명시적 가정
```text
Java 21
Spring Boot 4.1 BOM
Gradle Kotlin DSL
root package: io.backend.skeleton.web
stable modules: modules/web
advanced modules: modules/web-advanced
```
실제 저장소가 포함되지 않았으므로 이 패키지는 설계·계획·정적 검증 산출물이며, Gradle compile과 서버·DB·Redis·Nginx 통합 시험 결과를 포함하지 않습니다.
+193
View File
@@ -0,0 +1,193 @@
# Web Superpowers 문서 정적 검증 결과
검증 기준일: 2026-08-13
## 1. 검증 대상
| 문서 | 행 수 |
|---|---:|
| `web-inbound-http-api-execution-platform-design.md` | 1,656 |
| `web-inbound-http-api-execution-platform-implementation-plan.md` | 4,762 |
| `web-advanced-capabilities-expansion-plan.md` | 1,532 |
| 원본 심층 리서치 | 1,597 |
## 2. 구조 검증
| 항목 | 결과 |
|---|---:|
| 실행 검사 | 1,097 |
| 통과 | 1,097 |
| 실패 | 0 |
| Stable Task | 58 |
| Advanced Task | 19 |
| Stable Create 경로 | 289 |
| Advanced Create 경로 | 94 |
| Stable Task 번호 연속성 | PASS |
| Advanced Task 번호 연속성 | PASS |
| 모든 Task의 Files·Interfaces | PASS |
| 모든 Task의 Implementation requirements | PASS |
| 모든 Task의 Step 1~5 | PASS |
| 모든 Task의 실패·통과 예상 결과 | PASS |
| 모든 Task의 Git commit 명령 | PASS |
| Stable Create 경로 중복 | 없음 |
| Advanced Create 경로 중복 | 없음 |
| Stable·Advanced Create 경로 충돌 | 없음 |
| Markdown code fence 균형 | PASS |
| `TODO`·`TBD`·`FIXME` | 없음 |
검증 명령:
```bash
cd /mnt/data
./validate_web_docs.py
```
실행 결과:
```text
checks=1097 passed=1097 failed=0
```
## 3. 설계 핵심 계약 검증
다음 계약이 설계서와 계획서에 모두 존재하는지 확인했습니다.
```text
W1 / W2 / W3 / W4 공개 계층
MVC와 WebFlux Starter 상호 배타성
Request / Application / Response Evidence 분리
APPLICATION_COMMITTED와 HTTP response delivery 분리
RFC 9457 Problem Details
400 / 422 분리
409 / 412 분리
OpenAPI 3.1.2 Stable
Path major API version
HMAC keyset cursor
ETag / If-Match
Idempotency semantic fingerprint
Redis가 DB commit evidence의 유일한 source가 아님
Business mutation + JPA idempotency evidence same transaction
Application commit 후 response-loss fault test
Durable acceptance 이후에만 202
Trusted Nginx forwarded-header boundary
Tomcat / Jetty / Reactor Netty / Nginx 실제 gate
Rate limit 429와 Admission 503 분리
Low-cardinality metric·access log
Stable·Advanced dependency 격리
```
## 4. 계획 완결성 검증
Stable 계획은 다음 단계로 구성됩니다.
```text
Task 1~14
→ Core·HTTP·JSON·Problem·Architecture Foundation
Task 15~22
→ MVC·Tomcat·Jetty·WebFlux·Reactor Netty
Task 23~30
→ Security·Proxy·Versioning·Route·OpenAPI
Task 31~36
→ Collection Query·Cursor·Conditional·Evidence
Task 37~43
→ Idempotency·JPA Evidence·Redis Gate·Response Loss
Task 44~48
→ Durable Operation·Outbox·HTTP Resource·Cache
Task 49~55
→ Budget·Rate·Admission·CORS/CSRF·Order·Observability·Nginx
Task 56~58
→ Cross-stack Contract·Performance·Stable Release
```
Advanced 계획은 다음 단계로 구성됩니다.
```text
Task 1~3
→ Module isolation·Virtual Thread·Controlled Blocking Bridge
Task 4~5
→ JSON Merge Patch·JSON Patch
Task 6~13
→ Streaming Core·SSE·NDJSON·JSON Sequence·Drain·Replay
Task 14~18
→ Functional WebFlux·CBOR·XML·OpenAPI 3.2·RateLimit Draft
Task 19
→ Soak·Rollback·Promotion Gate
```
## 5. 검증 범위의 한계
현재 검증은 **문서의 구조, 요구사항 추적성, 내부 계약, 파일 경로, 작업 순서에 대한 정적 검증**입니다.
실제 Backend Skeleton 저장소가 이번 입력에 포함되지 않았으므로 다음은 실행한 상태가 아닙니다.
```text
Gradle configuration·compile
Spring Boot ApplicationContext 기동
Tomcat·Jetty·Reactor Netty 실제 contract
PostgreSQL JPA idempotency transaction
Redis concurrent gate
Messaging outbox operation
Nginx TLS·Forwarded topology
commit 후 TCP reset fault injection
OpenAPI generated client compile
abuse·load·graceful shutdown
Git commit
```
계획서의 경로와 package는 다음 명시적 가정을 사용합니다.
```text
Java 21
Gradle Kotlin DSL
Spring Boot 4.1 BOM
root package: io.backend.skeleton.web
stable module root: modules/web
advanced module root: modules/web-advanced
```
## 6. 패키지 무결성 검증
패키지 조립 후 다음 검증을 추가로 수행했습니다.
```text
Package-local validator
→ checks=1104 passed=1104 failed=0
MANIFEST.sha256
→ 모든 7개 파일 OK
ZIP CRC
→ No errors detected
독립 문서와 패키지 내부 문서
→ byte 단위 일치
```
검증 명령:
```bash
cd /mnt/data/web-superpowers-package
./validate_web_docs.py
sha256sum -c MANIFEST.sha256
cd /mnt/data
unzip -t web-superpowers-package.zip
cmp web-inbound-http-api-execution-platform-design.md \
web-superpowers-package/docs/superpowers/specs/2026-08-13-web-inbound-http-api-execution-platform-design.md
cmp web-inbound-http-api-execution-platform-implementation-plan.md \
web-superpowers-package/docs/superpowers/plans/2026-08-13-web-inbound-http-api-execution-platform-implementation-plan.md
cmp web-advanced-capabilities-expansion-plan.md \
web-superpowers-package/docs/superpowers/plans/2026-08-13-web-advanced-capabilities-expansion-plan.md
```
File diff suppressed because it is too large Load Diff
+286
View File
@@ -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)