Files
llm-wiki/raw/branch-notes/feature-streaming-response-contract.md

31 KiB

title, source_type, status, branch, parent_branch, related_projects, tags, created, last_reviewed, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
title source_type status branch parent_branch related_projects tags created last_reviewed target_merge status_label id kind project work_item inherits refines overrides depends_on contract_packet contract_packet_sha256
branch / feature-streaming-response-contract branch-note verified feature-streaming-response-contract
ca-skeleton
branch
ca-skeleton
streaming
sse
websocket
async
2026-05-31 2026-06-04 in-progress BR-CA-SKELETON-OPERATIONAL-CONTRACT-045 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-045
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-API-VERSIONING-001@1
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-RESILIENCE-001@1
1 3a771345ceaa06edbf916bcf7a52f6ade375f5415fe2ed9b6e0b914fab6c41a4

branch: feature-streaming-response-contract

Layer: raw/branch-notes/ — Server-Sent Events (SSE) / WebSocket / long-polling / chunked streaming 응답의 지원 여부 + 도입 시 계약을 정의합니다. 완료 후 /ingestwiki/projects/ 에만 추출합니다. status_label: in-progress | review | merged | abandoned

🟢🟡 D3 구현 완료 — 2026-06-02. 본 branch 는 두 갈래로 분리됩니다 (결정 §결정 사항):

  • 🟢 IN-SCOPE (구현 완료): ca-skeleton 은 이벤트/server-push 스트리밍(SSE·WebSocket)을 미지원으로 확정 (D1) 하고, 이를 ArchUnit rule 로 정적 차단 (D3). SseEmitter / ResponseBodyEmitter / WebSocket 계열 import 차단. — locally-verified (2026-06-02).
  • 🟡 DEFERRED (보류): 이벤트 스트리밍을 지원하기로 했을 때 의 매커니즘·envelope·per-event span 계약 (D2). 재개 트리거 = 실제 server→client push use case (실시간 알림 / LLM token streaming) 또는 통신/전송 프로토콜 계약 branch 착수. 6개 근거 raw 는 §Sources 에 보존.

용어 주의 (핵심): 본 branch 의 "streaming response" = 이벤트/server-push 스트리밍 (통신 모델이 request-response → server-push 로 바뀌는 것). StreamingResponseBody (대용량 파일 다운로드용 응답 body 청크 전송 — 통신 모델은 여전히 request-response) 는 별개 관심사이며 본 branch 범위 밖raw/branch-notes/feature-file-resource-handling-contract D8 이 소유. 차단 대상 아님 (R3 OUT_OF_BRANCH_SCOPE).

부모 (필수)

이 branch 가 어느 작업 묶음에 속하는지. 본 branch 는 project-note 의 직접 자식 (parent_branch: 비어 있음).

ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 §3 Structured API Response Contract (envelope 정책) + §13 API Contract Surface + §8 Distributed Tracing Contract 의 운영 계약 중 streaming response 영역을 정제한다.

형제 branch (cross-cite 후보)

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: 지원 protocol과 timeout·failure contract test가 고정된다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-API-VERSIONING-001@1 URI prefix /v1이 default이며 X-Api-Version은 compatibility 실험용 보조 header다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-RESILIENCE-001@1 Resilience4j가 default이며 Spring Retry는 simple blocking retry에만 허용한다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

목표

ca-skeleton 의 현재 default 는 request-response 동기 응답 만 지원합니다. SSE / WebSocket / long-polling / chunked streaming 은 envelope 정책 적용 모호 + 운영 부담 (connection 수, timeout, load balancer 설정 차이) 이 큰 영역.

본 branch 의 1차 결정: ca-skeleton 이 streaming 을 지원 할지 명시적으로 미지원 할지.

만약 지원 결정이면:

  1. 어떤 streaming 매커니즘 (SSE / WebSocket / long-poll / chunked-transfer-encoding)
  2. event envelope shape (envelope.success/error 적용 여부, event header)
  3. trace context 전파 (한 connection 에 multiple event 의 traceId 정책)
  4. timeout / heartbeat / reconnect 정책
  5. observability (connection metric / event throughput / error rate)
  6. load balancer / reverse proxy 설정 의무 (sticky session? keep-alive 시간?)

만약 미지원 결정이면:

  1. 정확한 out of scope 라벨링
  2. 대안 매커니즘 안내 (webhook outbound, polling endpoint)
  3. controller 에서 streaming API 사용 금지 ArchUnit rule (StreamingResponseBody, SseEmitter, @WebSocket 등 import 차단)
  • 이슈:
  • PR:

범위

포함 범위 (🟢 결정 완료 — 착수 가능)

  • 이벤트/server-push 스트리밍 지원 여부 결정 → D1: 미지원 확정
  • 미지원의 ArchUnit 정적 강제 → D3: no_sse_emitter / no_response_body_emitter / no_websocket_handler (§구현 가이드)

Deferred (🟡 D2 — 지원 시 계약, 보류)

  • 지원 결정 시 매커니즘 선택 (SSE / WebSocket / long-poll / chunked) — 재개 시 SSE 우선 (D2)
  • 지원 결정 시 event envelope shape + per-event trace context 전파 (tracing branch 와 OPEN)
  • 지원 결정 시 timeout / heartbeat / reconnect / connection cap
  • 지원 결정 시 observability metric + reverse proxy 설정 가이드

제외 범위

근거 (필수, 최소 1개+)

본 branch 의 결정 근거 (D1/D3 in-scope + D2 보류분).

2026-06-02: wiki-decision-researcher 가 streaming 매커니즘 비교를 위해 아래 6개 raw 를 아카이빙 완료 (각 verbatim quote + self-grep 검증 + 본 branch 로 Parent upward link). D2(지원 계약) 재개 시 재조사 없이 재사용.

Source 정당화할 결정 영역 Claim ID 상태
raw/official-docs/whatwg-html-server-sent-events WHATWG HTML SSE spec (EventSource, retry, last-event-id, text/event-stream wire format) WHATWG-SSE-C1~C6 아카이빙 (official-standard)
raw/official-docs/rfc6455-websocket IETF RFC 6455 WebSocket protocol (full-duplex, HTTP Upgrade handshake, masking) RFC6455-C1~C6 아카이빙 (official-standard)
raw/official-docs/spring-mvc-async-streaming Spring MVC SseEmitter / ResponseBodyEmitter / StreamingResponseBody vendor doc SPRING-ASYNC-C1~C7 아카이빙 (official-vendor-doc)
raw/official-docs/rfc9112-http-1-1-chunked-transfer HTTP/1.1 chunked transfer encoding (§7.1 framing, HTTP/2 금지 경계) RFC9112-CHUNK-C1~C5 아카이빙 (official-standard)
raw/official-docs/rfc9110-http-semantics (기존) HTTP semantics — 단 C1~C22 는 모두 다른 branch 귀속, streaming connection 의미론 claim 없음 (재개 시 §7.1 chunked 로 대체) 기존 raw (streaming traceability 단절)
raw/company-tech-blogs/sse-realtime-notification-woowahan 한국 사례 — SSE 운영 부담 (thundering herd, Pub/Sub fan-out, 4천만/일) WOOWA-SSE-C1~C6 아카이빙 (company-case-study — 공식 best practice 아님)
raw/company-tech-blogs/realtime-service-experience-woowahan-websocket 한국 사례 — WebSocket 운영 문제 (이벤트 유실, 모바일 네트워크, 클러스터링) WOOWA-WS-C1~C5 아카이빙 (company-case-study — 공식 best practice 아님)

미발견: kakao 공식 기술블로그 SSE/WebSocket 실운영 글 (JS-rendered 페이지 본문 추출 실패). 재개 시 접근 가능하면 보강.

TODO

각 항목 옆에 증거 등급 표기: actually-implemented | locally-verified | prod-verified | documented-only | planned | needs-confirmation

  • (P0) ca-skeleton 의 이벤트/server-push 스트리밍 지원 여부 결정 → D1: 미지원 확정 (2026-06-02)
  • (D3) ArchUnit rule 구현: no_sse_emitter, no_response_body_emitter, no_websocket_handlerCleanArchitectureTest 에 등록. violations-as-data fixtures (streaming/), over-block guard (allowed/streaming/), testCompileOnly 3종 추가. ArchUnit 33 rules, FixtureTest 24 tests — 모두 GREEN. — 등급: locally-verified (2026-06-02, 커밋 미확정 — 사용자가 직접 커밋 예정)
    • 품질 리뷰 후속 (2026-06-02): WebSocket fixture 테스트가 공유 VIOLATION_CLASSES 풀에서 평가되어 spring/jakarta 두 glob 중 하나만 동작해도 vacuous-pass 가능하던 갭 → spring·jakarta fixture 를 각각 ClassFileImporter.importClasses(...) 격리 corpus(SPRING_WEBSOCKET_FIXTURE_ONLY / JAKARTA_WEBSOCKET_FIXTURE_ONLY)로 평가하도록 수정. 각 glob 독립 검증. annotation-only fixture 라 격리 import 시 link-time 클래스 로딩 안전(NoClassDefFoundError 없음, 24 tests GREEN 재확인).
  • 지원 결정 시 매커니즘 trade-offD2 보류. 재개 시 §Sources 6개 raw 로 비교 (SSE 우선). — 등급: planned
  • 지원 결정 시 event envelope shape (envelope {success, data, meta} 적용? 별도 SSE event format event:...\ndata:...\n\n?) — 등급: planned
  • 지원 결정 시 trace context 전파 (W3C traceparent 가 한 connection 의 multiple event 에 어떻게 적용? per-event 새 span?) — 등급: planned
  • 지원 결정 시 timeout / heartbeat / reconnect (Last-Event-Id 활용 / connection idle timeout / heartbeat ping 간격) — 등급: planned
  • 지원 결정 시 reverse proxy 설정 의무 (Nginx proxy_buffering off, proxy_read_timeout, keep-alive) — 등급: planned
  • 지원 결정 시 connection 수 cap (per-user / per-IP / per-tenant) — DoS 방어 — 등급: planned

진행 중 메모

  • 본 branch 의 1차 결정 은 사실상 "ca-skeleton minimalist 정신" vs "도입 필요성" 의 trade-off. 현재 sample-portfolio fixture 가 streaming 시나리오 없으므로 미지원 default + ArchUnit 차단 이 가장 자연스러운 default 일 가능성 — 단 실제 사용자 도메인이 추가될 때 도입 가능성 열어둠.
  • 미지원 결정의 핵심 cost: streaming 이 필요한 use case (real-time notification, large file streaming, server-push) 가 등장하면 webhook outbound 또는 polling 으로 우회 — raw/branch-notes/feature-webhook-outbound-contract 가 webhook 대안 SSOT.

결정 사항

  • D1 (2026-06-02): 이벤트/server-push 스트리밍 미지원 확정. ca-skeleton 은 SSE / WebSocket 등 server-push 이벤트 스트리밍을 default 로 지원하지 않는다. (통신 모델을 request-response → server-push 로 바꾸는 영역)

    • 사유 ① streaming-response 는 독립 결정이 아니라 통신/전송 프로토콜 계약(HTTP vs gRPC vs streaming)의 한 facet. 전송 프로토콜은 skeleton 에서 가장 마지막에 고정 해야 할 영역 (가장 덜 보편적, 모든 파생 프로젝트의 기본기로 박을 근거 약함).
    • 사유 ② 현 sample-portfolio fixture 에 server-push use case 부재 (YAGNI / speculative generality 회피).
    • 사유 ③ sibling raw/branch-notes/feature-api-contract-baseline 가 이미 verified scope 에 "ca-skeleton 은 request-response 만 지원, SSE/WS 도입은 별도 branch" 선언 — 일관성.
    • 범위 명확화 (R3): 미지원 대상은 이벤트 스트리밍(server-push) 이지, StreamingResponseBody 기반 대용량 다운로드(응답 body 청크 전송) 가 아니다. 후자는 request-response 모델 내 다운로드 최적화이며 raw/branch-notes/feature-file-resource-handling-contract D8 이 소유. 본 branch 차단 대상에서 제외.
  • D3 (2026-06-02): 이벤트 스트리밍 미지원을 ArchUnit rule 로 정적 강제 (IN-SCOPE, 착수 가능). D1 을 코드 단계에서 강제 — controller/adapter 가 이벤트 스트리밍 API 를 import 하면 build 실패.

    • rule: no_sse_emitter, no_response_body_emitter, no_websocket_handler (상세 명세는 §구현 가이드).
    • 메커니즘 선례: raw/branch-notes/feature-boundary-validation-mapping-contract D5 (no_problem_detail_usage = import-level 차단), 동 branch 가 ArchUnit suite host, archunit-junit5 1.3.0 (project §34 Stack Commitment).
    • 근거: SPRING-ASYNC-C4 (SseEmitter = ResponseBodyEmitter subclass, W3C SSE 포맷), SPRING-ASYNC-C3 (ResponseBodyEmitter = 객체 stream emit) — 차단 대상 클래스의 vendor 정의.
  • D2 (2026-06-02): 이벤트 스트리밍 지원 계약 = 보류 (Deferred). 만약 지원하기로 하면 필요한 매커니즘 선택(SSE vs WebSocket) / event envelope shape / per-event trace span / timeout·heartbeat·reconnect / connection cap / reverse proxy 의무 — 모두 보류.

    • 재개 트리거: (a) 실제 server→client push use case 등장 (실시간 알림 / LLM token streaming / 대용량 export 진행률), 또는 (b) 통신/전송 프로토콜 계약 branch 착수 (예정 — 생성 시 본 branch D2 를 forward-ref). 재개 시 §Sources 6개 raw 로 되묻지 않고 진행 가능.
    • 재개 시 우선 매커니즘: SSE (SseEmitter) — 단방향 push 에 적합, 기존 HTTP 인프라 재사용, WebSocket 대비 proxy 부담 낮음. 근거: WHATWG-SSE-C1~C3 (official-standard) + SPRING-ASYNC-C4 (official-vendor-doc). 단 재개 시 D3 ArchUnit rule 의 SSE 차단을 명시적으로 해제해야 함.

결정-근거 매핑

Decision ID Decision Supporting Claims Evidence Strength Open Risk
D1 이벤트/server-push 스트리밍(SSE·WebSocket) 미지원 확정 SPRING-ASYNC-C4 (SseEmitter=W3C SSE), WHATWG-SSE-C5 (단방향 push), RFC6455-C1 (full-duplex) — 무엇을 미지원하는지 의 클래스 정의 + raw/branch-notes/feature-api-contract-baseline scope 선언 (request-response only) = UNSUPPORTED_DECISION (project-internal consistency 논거 — api-baseline Out of scope 선언과의 정합, 보조 근거) official-standard + official-vendor-doc + cross-branch-consistency 실제 push use case 등장 시 D2 로 재개. StreamingResponseBody(다운로드)와 혼동 금지 — 별 관심사
D3 D1 을 ArchUnit import-ban 으로 정적 강제 (IN-SCOPE): no_sse_emitter / no_response_body_emitter / no_websocket_handler SPRING-ASYNC-C4 (SseEmitter FQN + 역할), SPRING-ASYNC-C3 (ResponseBodyEmitter = 객체 stream emit) + 선례 raw/branch-notes/feature-boundary-validation-mapping-contract D5 (import-level 차단 메커니즘) + project-ssot (raw/project-notes/ca-skeleton-operational-contract §34 Stack Commitment — archunit-junit5 1.3.0 lock) official-vendor-doc (차단 대상 클래스) + cross-branch-SSOT (ArchUnit suite host = boundary branch) + project-ssot rule 명명 + 차단 메커니즘(import vs reference) = UNSUPPORTED_IMPL_DECISION (§구현 가이드). WebFlux 타입은 stack(MVC) 밖이라 미포함
D2 이벤트 스트리밍 지원 계약(매커니즘/envelope/per-event span/timeout/cap/proxy) = 보류 (Deferred) WHATWG-SSE-C1~C6, RFC6455-C1~C6, RFC9112-CHUNK-C1~C5, SPRING-ASYNC-C1~C7, WOOWA-SSE-C1~C6, WOOWA-WS-C1~C5 (재개 시 재조사 불필요하게 보존) official-standard + official-vendor-doc + company-case-study 재개 트리거 전까지 dormant. 아래 OPEN 갭(per-event span) 동반

교차 계약 의존 정리:

  • (D3 의존, 활성) ArchUnit suite host = raw/branch-notes/feature-boundary-validation-mapping-contract (D5 import-ban 선례). 본 branch 의 3개 rule 은 그 suite 에 등록. archunit-junit5 1.3.0 (project §34). suite 구조가 바뀌면 본 branch rule 영향.
  • (폴링 대안, 닫힘) server-push 미지원 시 비동기 응답 우회 = raw/branch-notes/feature-api-contract-baseline D17 (verified LRO: 202 + Location + polling endpoint + Retry-After). webhook 우회는 raw/branch-notes/feature-webhook-outbound-contract 가 미결정(forward-ref) + 동 branch 가 SSE 대체를 본 branch 로 역참조 → 상호 보류.
  • (D3 경계, OUT_OF_BRANCH_SCOPE) StreamingResponseBodyraw/branch-notes/feature-file-resource-handling-contract D8 (verified, 다운로드 메커니즘) 소유 → 차단 안 함. blanket ban 시 파일 다운로드 build 실패하므로 명시적 제외.
  • (D2 동반 OPEN, 미해결) per-event trace span: raw/branch-notes/feature-distributed-tracing-contract D5/D7 은 traceparent 를 request 단위 로 전파하나 propagation surface(outbound HTTP/Kafka/Rabbit/scheduler)에 long-lived streaming connection 없음. "한 connection 의 N개 event 에 traceId 를 어떻게" 는 기존 Decision ID 로 닫히지 않는 진짜 OPEN 갭 — D2 재개 시 tracing branch 와 동시 결정 필요.

구현 가이드

본 §는 D3 (이벤트 스트리밍 미지원의 ArchUnit 정적 강제) 만 명세한다. D2 (지원 계약) 는 보류이므로 구현 명세 없음 — 재개 시 작성.

1. 이벤트 스트리밍 차단 ArchUnit rules (D3)

Trace: D1 (이벤트/server-push 스트리밍 미지원) → D3 (ArchUnit 정적 강제). 차단 대상 클래스 정의 = SPRING-ASYNC-C4 (SseEmitter = ResponseBodyEmitter subclass, W3C SSE 포맷), SPRING-ASYNC-C3 (ResponseBodyEmitter = 객체 stream emit). 메커니즘 선례 = raw/branch-notes/feature-boundary-validation-mapping-contract D5 (no_problem_detail_usage import-level 차단). suite host = boundary branch ArchUnit suite, archunit-junit5 1.3.0 (project §34).

  • UNSUPPORTED_IMPL_DECISION:
    • ① rule 이름 no_sse_emitter / no_response_body_emitter / no_websocket_handler — 임의 명명 (boundary D5 명명 컨벤션 차용, 근거 raw 가 이름 권고 안 함).
    • import-level dependOnClassesThat() vs reference-level 차단 의 메커니즘 선택 — boundary D5 와 동일 trade-off (false positive 회피 vs 회귀 차단). import-level 채택은 사용자 결정.
    • WebSocket 차단 FQN 범위 — RFC 6455 (RFC6455-C*) 는 프로토콜 만 다루고 Spring/Jakarta API 클래스를 열거하지 않음. 아래 FQN 목록(spring-websocket 패키지 + STOMP + Jakarta) 은 사용자가 선정한 차단 표면.
    • ResponseBodyEmitter 포함 여부 — D1 은 "이벤트 스트리밍" 미지원. ResponseBodyEmitter 는 SSE 의 base 이자 incremental 객체-emit 메커니즘(SPRING-ASYNC-C3)이므로 차단에 포함. 단 비-SSE JSON object-stream 용도까지 막는 것은 사용자 판단 (server-push 성격으로 간주).
    • 차단 scope = production code only (ImportOption.DoNotIncludeTests) — 테스트에서 차단 위반 재현용 fixture 작성 가능하도록.
rule 이름 차단 대상 FQN 메커니즘 비고
no_sse_emitter org.springframework.web.servlet.mvc.method.annotation.SseEmitter noClasses().should().dependOnClassesThat().haveFullyQualifiedName(...) SSE server-push 의 직접 표면 (SPRING-ASYNC-C4)
no_response_body_emitter org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter 동일 SSE 의 base + incremental 객체 emit (SPRING-ASYNC-C3). SseEmitter 가 이를 상속하므로 함께 차단. 비-SSE JSON object-stream 용도까지 server-push 모델로 간주해 차단 (UNSUPPORTED_IMPL_DECISION④)
no_websocket_handler org.springframework.web.socket.. (패키지 전체) + jakarta.websocket.. (패키지 전체) dependOnClassesThat().resideInAnyPackage(...) spring-websocket(WebSocketHandler, @EnableWebSocket, @EnableWebSocketMessageBroker/STOMP) + Jakarta @ServerEndpoint 계열 일괄 차단 (RFC6455-C1 full-duplex 모델). WebSocket 표면은 단일 FQN 이 없어 haveFullyQualifiedName 대신 resideInAnyPackage glob 불가피 (UNSUPPORTED_IMPL_DECISION③)

명시적 비-차단 (R3 OUT_OF_BRANCH_SCOPE):

  • org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody차단하지 않음. 대용량 다운로드용 응답 body 청크 전송 (SPRING-ASYNC-C2: "for example, for a file download"), 통신 모델은 request-response 유지. raw/branch-notes/feature-file-resource-handling-contract D8 (verified) 소유. blanket ban 시 다운로드 build 실패.
  • WebFlux reactive 타입(Flux<ServerSentEvent> 등) — ca-skeleton stack 은 spring-webmvc(SPRING-ASYNC-C1/C5 의 servlet 전제)이므로 해당 클래스가 classpath 에 없음 → rule 불필요 (재개 시 WebFlux 전환하면 별도 검토).

2. 미지원 시 대안 경로 (문서화만, 코드 없음)

Trace: D1 미지원 결정의 운영 cost 흡수 경로. 코드는 본 branch 가 작성하지 않음 — 기존 형제 branch 결정 재사용.

엣지·실패·의존

R4 캡처. D3 (ArchUnit 차단) 구현 중 부딪힐 실패/엣지/계약 의존. D2 (지원 계약) 는 보류이므로 그쪽 엣지(connection 끊김/reconnect/cap 초과)는 재개 시 작성 — 여기서는 "OPEN" 으로만 표시.

  • 실패·엣지 경로 (D3 차단 rule):
    • false positive — StreamingResponseBody 오차단: rule glob 이 너무 넓으면 file-resource D8 다운로드가 깨짐. 기대 동작 = StreamingResponseBody 는 rule 대상 FQN 목록에 포함하지 않음 (§구현 가이드 명시적 비-차단). 차단되면 안 됨.
    • transitive dependency 차단: dependOnClassesThat() 는 transitive import 도 잡으므로, 제3 라이브러리가 내부적으로 SseEmitter 를 참조하면 false positive 가능. 기대 동작 = production source 의 직접 의존만 위반으로 본다 (필요 시 ImportOption 으로 vendor 패키지 제외 — UNSUPPORTED_IMPL_DECISION). 해소 선례: raw/branch-notes/feature-boundary-validation-mapping-contract §구현 가이드 §8 no_problem_detail_usage 의 import-level 차단 trade-off 참조.
    • WebSocket 패키지 glob 과다: org.springframework.web.socket.. 전체 차단이 의도치 않은 하위 유틸까지 막을 수 있음. 기대 동작 = WebSocket 미사용이 default 이므로 전체 차단이 안전, 도입 시 rule 해제.
    • 테스트 코드: 차단 위반 재현 fixture 는 test 에 있어야 하므로 rule scope = DoNotIncludeTests (production만).
  • 다른 계약 의존:
  • D2 보류분 OPEN 의존 (재개 시): raw/branch-notes/feature-distributed-tracing-contract D5/D7 — long-lived connection 의 per-event trace span 정책 미존재. 재개 시 동시 결정 필요.

검증해야 할 주장

Claim Why uncertain How to verify Status
no_sse_emitter / no_response_body_emitter 가 production 코드의 SseEmitter·ResponseBodyEmitter import 를 build 단계에서 실제 차단 ArchUnit rule 작성 후 실측 전까지 동작 미보장 violations-as-data fixtures (SseEmitterUsingFixture, ResponseBodyEmitterUsingFixture) → FixtureTest GREEN locally-verified (2026-06-02)
no_websocket_handlerorg.springframework.web.socket.. + jakarta.websocket.. glob 이 정확히 WebSocket 표면만 차단 (over-block 없음) 패키지 glob 범위가 vendor 패키지 구조에 의존 SpringWebSocketHandlerFixture (@EnableWebSocket), JakartaWebSocketEndpointFixture (@ServerEndpoint) fixtures → FixtureTest GREEN locally-verified (2026-06-02)
StreamingResponseBody 가 rule 에 걸리지 않아 file-resource D8 다운로드가 정상 build blanket ban 시 false positive 위험 (핵심 회귀) StreamingResponseBodyAllowedFixture over-block guard → 3개 D3 rule 모두 hasViolation() == false — GREEN locally-verified (2026-06-02)
ArchUnit suite host(boundary branch) 에 3개 rule 등록이 기존 suite 와 충돌 없음 suite 구조·rule 명명 충돌 가능 CleanArchitectureTest 33 tests (기존 30 + D3 3) — 0 failures locally-verified (2026-06-02)

마주친 문제

  • testCompileOnly + class loading: SpringWebSocketHandlerFixture 최초 버전은 TextWebSocketHandler 를 extends — JUnit 이 fixture 클래스를 로드할 때 NoClassDefFoundError 발생 (testCompileOnly jar 는 runtime classpath 에 없으므로). 해결: @EnableWebSocket annotation 참조만으로 교체. Annotation 은 JVM 에서 lazy access (class load 시 필요 없음) → ArchUnit bytecode 분석은 정상 동작. 패턴: annotation-only reference 는 testCompileOnly fixture 에서 runtime-safe.
  • jakarta.websocket-api 2.1.1 는 server-only: jakarta.websocket-api 2.1.1 jar 는 jakarta.websocket.server.* 만 포함 (Session, OnMessage 등 base 패키지 없음). jakarta.websocket-all 또는 client jar 가 필요. @ServerEndpoint (server 패키지) 만으로 fixture 재작성.

묶음 (이 branch에서 파생된 자료)

Sub-branches (세부 작업)

  • (없음)

오류 기록 (이 branch 작업 중 발생)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

강의 (이 작업을 위해 학습한 강의)

  • (없음)

job-posting tie-ins (이 작업에서 파생된 글감)

관련 일일 노트

  • (없음 — scaffolding 단계)

완료 후 정리

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목: D3 ArchUnit 차단 rule 3개(no_sse_emitter / no_response_body_emitter / no_websocket_handler) + violations-as-data fixtures + over-block guard + StreamingResponseBody 비-차단 경계 → wiki/projects/ca-tmpl/streaming-response-support
    • locally-verified 항목: CleanArchitectureTest + ArchitectureViolationFixtureTest GREEN (rule catch + over-block guard + spring/jakarta 격리 corpus) → 동 문서 §로컬/dev 검증
    • prod-verified 항목: 없음 (스트리밍 미지원이므로 운영 streaming 지표 부재)
  • 추출하지 않을 항목 (planned / documented-only / abandoned): D2 스트리밍 지원 계약 전체(매커니즘/envelope/per-event span/timeout/cap/proxy) = planned 보류. 일반 개념(SSE/WebSocket/long-poll/chunked trade-off)은 canonical wiki/concepts/streaming-response-patterns 로 분리.

/ingest 처리 (2026-06-04, ca-tmpl @9693d72 ground-truth 대조): 본 branch 의 D3(미지원 ArchUnit 강제)를 wiki/projects/ca-tmpl/streaming-response-support.md 로 추출(CREATE), 일반 개념을 wiki/concepts/streaming-response-patterns.md 로 추출(CREATE). production 코드에 streaming import 0건 확인 — 미지원(ban)이 실제. honest framing: 구현된 것은 차단 가드레일 이지 스트리밍 지원 아님. status → verified.