Files
clean-architecture-backend-…/docs/websocket-superpowers-package/docs/superpowers/plans/2026-08-14-websocket-advanced-capabilities-expansion-plan.md
T

84 KiB
Raw Blame History

WebSocket Advanced Capability Expansion Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Stable Raw Typed WebSocket의 security·evidence·budget·writer 계약을 유지하면서 durable resume, multi-node fan-out, STOMP/Broker Relay, Binary Codec, compression, outbound client, SockJS와 HTTP/2·3 compatibility를 선택적으로 추가한다.

Architecture: 모든 Advanced 모듈은 modules/websocket-advanced에 격리되고 Stable public contract를 소비한다. Resume history와 durable cross-node delivery는 Messaging Platform이 소유하며 Redis는 session index·presence·ephemeral fan-out에만 사용한다. STOMP는 별도 Protocol Adapter이고 RECEIPT·ACK·destination 의미를 Stable Raw evidence로 평탄화하지 않는다.

Tech Stack: Java 21, Spring Boot 4.1 BOM, Stable WebSocket modules, Spring Messaging/STOMP, RabbitMQ STOMP relay, Redis, Messaging Platform, Protobuf, Jackson CBOR, RFC 7692, Playwright, Nginx/Ingress HTTP/2·3 test path.

Global Constraints

  • Stable Task 153과 Stable Release Gate가 완료된 뒤 시작한다.
  • Stable Starter는 Advanced module을 의존하지 않는다.
  • 모든 Advanced 기능은 backend.websocket.advanced.* feature flag를 요구한다.
  • Advanced 기능도 Stable Origin·Authentication·Evidence·Budget·Writer·Observability 계약을 우회하지 않는다.
  • durable replay·offset·DLQ는 Messaging이 소유한다.
  • Redis Pub/Sub을 durable replay라고 선언하지 않는다.
  • actual transport session을 Redis에 직렬화하지 않는다.
  • STOMP RECEIPT을 Application Commit으로 승격하지 않는다.
  • STOMP ACK을 보편적 exactly-once로 표현하지 않는다.
  • Simple Broker는 Local/Test 단일 node에 제한한다.
  • Protobuf·CBOR도 대형 file bytes transport를 제공하지 않는다.
  • permessage-deflate는 기본 비활성이고 endpoint별 benchmark 후 opt-in한다.
  • HTTP/2는 Compatibility, HTTP/3은 Experimental 등급을 유지한다.
  • GraphQL bridge는 GraphQL operation·error·subscription semantics를 소유하지 않는다.
  • 모든 task는 red-green TDD와 독립 commit으로 끝난다.

Execution Baseline

Stable Task 153 PASS
→ Advanced Task 122
→ 기능별 Promotion Gate

Task 1: Advanced 모듈 그래프와 Feature Flag 격리

**Files:**
- Create: `modules/websocket-advanced/websocket-advanced-bootstrap/src/main/java/io/backend/skeleton/websocket/advanced/WebSocketAdvancedModuleCatalog.java`
  • Create: modules/websocket-advanced/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-advanced-bootstrap/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-resume/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-cluster-redis/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-cluster-messaging/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-presence-redis/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-stomp/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-broker-relay-rabbit/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-protobuf/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-cbor/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-compression/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-outbound-client/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-sockjs-compat/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-http2-compat/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-http3-experimental/build.gradle.kts

  • Create: modules/websocket-advanced/websocket-graphql-transport-bridge/build.gradle.kts

  • Test: modules/websocket-advanced/websocket-advanced-bootstrap/src/test/java/io/backend/skeleton/websocket/advanced/WebSocketAdvancedModuleCatalogTest.java

    Interfaces:

    • Consumes: Stable Task 153 Release Gate.
    • Produces: Advanced dependency graph와 backend.websocket.advanced.* opt-in flags.

    Implementation requirements:

    • Stable starter가 advanced module을 의존하지 않는다.
  • 모든 advanced bean은 explicit feature flag를 요구한다.

  • Stable public evidence·security·budget contract를 우회하지 않는다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.advanced;
    
    

class WebSocketAdvancedModuleCatalogTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketAdvancedModuleCatalog.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("modules", "featureFlags"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-advanced-bootstrap:test --tests 'io.backend.skeleton.websocket.advanced.WebSocketAdvancedModuleCatalogTest'
```

Expected: FAIL because `WebSocketAdvancedModuleCatalog` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.advanced;

public record WebSocketAdvancedModuleCatalog( java.util.Set modules, java.util.Set featureFlags) { public WebSocketAdvancedModuleCatalog { java.util.Objects.requireNonNull(modules, "modules"); java.util.Objects.requireNonNull(featureFlags, "featureFlags"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-advanced-bootstrap:test --tests 'io.backend.skeleton.websocket.advanced.WebSocketAdvancedModuleCatalogTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-advanced-bootstrap/src/main/java/io/backend/skeleton/websocket/advanced/WebSocketAdvancedModuleCatalog.java' 'modules/websocket-advanced/websocket-advanced-bootstrap/src/test/java/io/backend/skeleton/websocket/advanced/WebSocketAdvancedModuleCatalogTest.java' 'modules/websocket-advanced/build.gradle.kts' 'modules/websocket-advanced/websocket-advanced-bootstrap/build.gradle.kts' 'modules/websocket-advanced/websocket-resume/build.gradle.kts' 'modules/websocket-advanced/websocket-cluster-redis/build.gradle.kts' 'modules/websocket-advanced/websocket-cluster-messaging/build.gradle.kts' 'modules/websocket-advanced/websocket-presence-redis/build.gradle.kts' 'modules/websocket-advanced/websocket-stomp/build.gradle.kts' 'modules/websocket-advanced/websocket-broker-relay-rabbit/build.gradle.kts' 'modules/websocket-advanced/websocket-protobuf/build.gradle.kts' 'modules/websocket-advanced/websocket-cbor/build.gradle.kts' 'modules/websocket-advanced/websocket-compression/build.gradle.kts' 'modules/websocket-advanced/websocket-outbound-client/build.gradle.kts' 'modules/websocket-advanced/websocket-sockjs-compat/build.gradle.kts' 'modules/websocket-advanced/websocket-http2-compat/build.gradle.kts' 'modules/websocket-advanced/websocket-http3-experimental/build.gradle.kts' 'modules/websocket-advanced/websocket-graphql-transport-bridge/build.gradle.kts'
git commit -m "build: advanced-feature-flag"
```

Task 2: Resume Token Wire Contract

**Files:**
- Create: `modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeTokenPayload.java`
  • Create: modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeTokenCodec.java

  • Create: modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeTokenKeyRing.java

  • Test: modules/websocket-advanced/websocket-resume/src/test/java/io/backend/skeleton/websocket/resume/WebSocketResumeTokenPayloadTest.java

    Interfaces:

    • Consumes: Stable sequence·connection context.
    • Produces: versioned·HMAC authenticated·actor/tenant bound resume token.

    Implementation requirements:

    • lastReceived가 아니라 lastApplied를 사용한다.
  • token은 actor·tenant·endpoint·protocol에 bind한다.

  • unknown key/version·expired·replay를 구분한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.resume;
    
    

class WebSocketResumeTokenPayloadTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketResumeTokenPayload.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("streamId", "lastAppliedSequence", "snapshotVersion", "expiresAt", "keyId"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-resume:test --tests 'io.backend.skeleton.websocket.resume.WebSocketResumeTokenPayloadTest'
```

Expected: FAIL because `WebSocketResumeTokenPayload` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.resume;

public record WebSocketResumeTokenPayload( String streamId, long lastAppliedSequence, String snapshotVersion, java.time.Instant expiresAt, String keyId) { public WebSocketResumeTokenPayload { java.util.Objects.requireNonNull(streamId, "streamId"); java.util.Objects.requireNonNull(snapshotVersion, "snapshotVersion"); java.util.Objects.requireNonNull(expiresAt, "expiresAt"); java.util.Objects.requireNonNull(keyId, "keyId"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-resume:test --tests 'io.backend.skeleton.websocket.resume.WebSocketResumeTokenPayloadTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeTokenPayload.java' 'modules/websocket-advanced/websocket-resume/src/test/java/io/backend/skeleton/websocket/resume/WebSocketResumeTokenPayloadTest.java' 'modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeTokenCodec.java' 'modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeTokenKeyRing.java'
git commit -m "feat: resume-token-wire-contract"
```

Task 3: Resume Coordinator와 Snapshot Fallback

**Files:**
- Create: `modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeDecision.java`
  • Create: modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeCoordinator.java

  • Create: modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketSnapshotProvider.java

  • Test: modules/websocket-advanced/websocket-resume/src/test/java/io/backend/skeleton/websocket/resume/WebSocketResumeDecisionTest.java

    Interfaces:

    • Consumes: Resume token과 sequence gap detector.
    • Produces: delta replay·snapshot fallback·permission check orchestration.

    Implementation requirements:

    • history loss에서 incremental apply를 계속하지 않는다.
  • permission changed 시 resume를 거부한다.

  • snapshot 이후 새 sequence 기준을 명시한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.resume;
    
    

class WebSocketResumeDecisionTest { @org.junit.jupiter.api.Test void valuesAreStable() { org.assertj.core.api.Assertions.assertThat(WebSocketResumeDecision.values()) .extracting(java.lang.Enum::name) .containsExactly("REPLAY_AVAILABLE", "SNAPSHOT_REQUIRED", "RESUME_DENIED", "RESUME_EXPIRED", "STREAM_GONE"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-resume:test --tests 'io.backend.skeleton.websocket.resume.WebSocketResumeDecisionTest'
```

Expected: FAIL because `WebSocketResumeDecision` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.resume;

public enum WebSocketResumeDecision { REPLAY_AVAILABLE, SNAPSHOT_REQUIRED, RESUME_DENIED, RESUME_EXPIRED, STREAM_GONE } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-resume:test --tests 'io.backend.skeleton.websocket.resume.WebSocketResumeDecisionTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeDecision.java' 'modules/websocket-advanced/websocket-resume/src/test/java/io/backend/skeleton/websocket/resume/WebSocketResumeDecisionTest.java' 'modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketResumeCoordinator.java' 'modules/websocket-advanced/websocket-resume/src/main/java/io/backend/skeleton/websocket/resume/WebSocketSnapshotProvider.java'
git commit -m "feat: resume-coordinator-snapshot-fallback"
```

Task 4: Messaging 기반 Durable Replay Bridge

**Files:**
- Create: `modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketReplayCursor.java`
  • Create: modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketDurableReplaySource.java

  • Create: modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketReplayEventMapper.java

  • Test: modules/websocket-advanced/websocket-cluster-messaging/src/test/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketReplayCursorTest.java

    Interfaces:

    • Consumes: Messaging Platform replay/offset API와 Resume Coordinator.
    • Produces: durable event history를 typed WebSocket event로 변환하는 bridge.

    Implementation requirements:

    • WebSocket이 broker offset ownership을 가져가지 않는다.
  • 원본 broker message를 client wire contract로 직접 노출하지 않는다.

  • duplicate·gap·schema mismatch를 검증한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.cluster.messaging;
    
    

class WebSocketReplayCursorTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketReplayCursor.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("streamId", "sequence", "messagingPosition"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cluster-messaging:test --tests 'io.backend.skeleton.websocket.cluster.messaging.WebSocketReplayCursorTest'
```

Expected: FAIL because `WebSocketReplayCursor` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.cluster.messaging;

public record WebSocketReplayCursor( String streamId, long sequence, String messagingPosition) { public WebSocketReplayCursor { java.util.Objects.requireNonNull(streamId, "streamId"); java.util.Objects.requireNonNull(messagingPosition, "messagingPosition"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cluster-messaging:test --tests 'io.backend.skeleton.websocket.cluster.messaging.WebSocketReplayCursorTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketReplayCursor.java' 'modules/websocket-advanced/websocket-cluster-messaging/src/test/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketReplayCursorTest.java' 'modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketDurableReplaySource.java' 'modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketReplayEventMapper.java'
git commit -m "feat: messaging-durable-replay-bridge"
```

Task 5: Redis External Session Index

**Files:**
- Create: `modules/websocket-advanced/websocket-cluster-redis/src/main/java/io/backend/skeleton/websocket/cluster/redis/WebSocketExternalSessionSummary.java`
  • Create: modules/websocket-advanced/websocket-cluster-redis/src/main/java/io/backend/skeleton/websocket/cluster/redis/WebSocketRedisSessionIndex.java

  • Test: modules/websocket-advanced/websocket-cluster-redis/src/test/java/io/backend/skeleton/websocket/cluster/redis/WebSocketExternalSessionSummaryTest.java

    Interfaces:

    • Consumes: Stable local registry와 Redis TTL capability.
    • Produces: 실제 session object 없는 TTL external index.

    Implementation requirements:

    • transport session을 Redis에 직렬화하지 않는다.
  • stale entry를 TTL과 node heartbeat로 제거한다.

  • actor/tenant 원문을 key에 넣지 않는다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.cluster.redis;
    
    

class WebSocketExternalSessionSummaryTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketExternalSessionSummary.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("connectionId", "nodeId", "actorFingerprint", "endpointName", "lastObservedAt"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cluster-redis:test --tests 'io.backend.skeleton.websocket.cluster.redis.WebSocketExternalSessionSummaryTest'
```

Expected: FAIL because `WebSocketExternalSessionSummary` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.cluster.redis;

public record WebSocketExternalSessionSummary( String connectionId, String nodeId, String actorFingerprint, String endpointName, java.time.Instant lastObservedAt) { public WebSocketExternalSessionSummary { java.util.Objects.requireNonNull(connectionId, "connectionId"); java.util.Objects.requireNonNull(nodeId, "nodeId"); java.util.Objects.requireNonNull(actorFingerprint, "actorFingerprint"); java.util.Objects.requireNonNull(endpointName, "endpointName"); java.util.Objects.requireNonNull(lastObservedAt, "lastObservedAt"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cluster-redis:test --tests 'io.backend.skeleton.websocket.cluster.redis.WebSocketExternalSessionSummaryTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-cluster-redis/src/main/java/io/backend/skeleton/websocket/cluster/redis/WebSocketExternalSessionSummary.java' 'modules/websocket-advanced/websocket-cluster-redis/src/test/java/io/backend/skeleton/websocket/cluster/redis/WebSocketExternalSessionSummaryTest.java' 'modules/websocket-advanced/websocket-cluster-redis/src/main/java/io/backend/skeleton/websocket/cluster/redis/WebSocketRedisSessionIndex.java'
git commit -m "feat: redis-external-session-index"
```

Task 6: Redis Ephemeral Fan-out

**Files:**
- Create: `modules/websocket-advanced/websocket-cluster-redis/src/main/java/io/backend/skeleton/websocket/cluster/redis/WebSocketEphemeralFanoutMessage.java`
  • Create: modules/websocket-advanced/websocket-cluster-redis/src/main/java/io/backend/skeleton/websocket/cluster/redis/WebSocketRedisEphemeralFanout.java

  • Test: modules/websocket-advanced/websocket-cluster-redis/src/test/java/io/backend/skeleton/websocket/cluster/redis/WebSocketEphemeralFanoutMessageTest.java

    Interfaces:

    • Consumes: Redis Pub/Sub at-most-once capability와 external session index.
    • Produces: lossy live signal fan-out adapter.

    Implementation requirements:

    • durable replay라고 선언하지 않는다.
  • 업무 payload 대신 bounded reference/event DTO를 사용한다.

  • expiresAt 이후 fan-out을 drop한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.cluster.redis;
    
    

class WebSocketEphemeralFanoutMessageTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketEphemeralFanoutMessage.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("targetNodeId", "messageType", "payloadReference", "expiresAt"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cluster-redis:test --tests 'io.backend.skeleton.websocket.cluster.redis.WebSocketEphemeralFanoutMessageTest'
```

Expected: FAIL because `WebSocketEphemeralFanoutMessage` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.cluster.redis;

public record WebSocketEphemeralFanoutMessage( String targetNodeId, String messageType, String payloadReference, java.time.Instant expiresAt) { public WebSocketEphemeralFanoutMessage { java.util.Objects.requireNonNull(targetNodeId, "targetNodeId"); java.util.Objects.requireNonNull(messageType, "messageType"); java.util.Objects.requireNonNull(payloadReference, "payloadReference"); java.util.Objects.requireNonNull(expiresAt, "expiresAt"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cluster-redis:test --tests 'io.backend.skeleton.websocket.cluster.redis.WebSocketEphemeralFanoutMessageTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-cluster-redis/src/main/java/io/backend/skeleton/websocket/cluster/redis/WebSocketEphemeralFanoutMessage.java' 'modules/websocket-advanced/websocket-cluster-redis/src/test/java/io/backend/skeleton/websocket/cluster/redis/WebSocketEphemeralFanoutMessageTest.java' 'modules/websocket-advanced/websocket-cluster-redis/src/main/java/io/backend/skeleton/websocket/cluster/redis/WebSocketRedisEphemeralFanout.java'
git commit -m "feat: redis-ephemeral-fan-out"
```

Task 7: Messaging Durable Cross-node Fan-out

**Files:**
- Create: `modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketDurableFanoutEnvelope.java`
  • Create: modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketMessagingFanoutAdapter.java

  • Test: modules/websocket-advanced/websocket-cluster-messaging/src/test/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketDurableFanoutEnvelopeTest.java

    Interfaces:

    • Consumes: Messaging publish/consumer contract와 local registry.
    • Produces: durable fan-out source를 current sessions로 전달하는 adapter.

    Implementation requirements:

    • broker ACK와 client ACK를 구분한다.
  • consumer redelivery에서 same sequence duplicate를 처리한다.

  • local outbound queue overflow 시 source ACK 정책을 명시한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.cluster.messaging;
    
    

class WebSocketDurableFanoutEnvelopeTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketDurableFanoutEnvelope.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("targetSelector", "messageType", "streamId", "sequence"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cluster-messaging:test --tests 'io.backend.skeleton.websocket.cluster.messaging.WebSocketDurableFanoutEnvelopeTest'
```

Expected: FAIL because `WebSocketDurableFanoutEnvelope` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.cluster.messaging;

public record WebSocketDurableFanoutEnvelope( String targetSelector, String messageType, String streamId, long sequence) { public WebSocketDurableFanoutEnvelope { java.util.Objects.requireNonNull(targetSelector, "targetSelector"); java.util.Objects.requireNonNull(messageType, "messageType"); java.util.Objects.requireNonNull(streamId, "streamId"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cluster-messaging:test --tests 'io.backend.skeleton.websocket.cluster.messaging.WebSocketDurableFanoutEnvelopeTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketDurableFanoutEnvelope.java' 'modules/websocket-advanced/websocket-cluster-messaging/src/test/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketDurableFanoutEnvelopeTest.java' 'modules/websocket-advanced/websocket-cluster-messaging/src/main/java/io/backend/skeleton/websocket/cluster/messaging/WebSocketMessagingFanoutAdapter.java'
git commit -m "feat: messaging-durable-cross-node-fan-out"
```

Task 8: Presence Summary

**Files:**
- Create: `modules/websocket-advanced/websocket-presence-redis/src/main/java/io/backend/skeleton/websocket/presence/WebSocketPresenceSummary.java`
  • Create: modules/websocket-advanced/websocket-presence-redis/src/main/java/io/backend/skeleton/websocket/presence/WebSocketPresenceStore.java

  • Test: modules/websocket-advanced/websocket-presence-redis/src/test/java/io/backend/skeleton/websocket/presence/WebSocketPresenceSummaryTest.java

    Interfaces:

    • Consumes: Local/external session lifecycle와 Redis TTL.
    • Produces: ONLINE·IDLE·STALE·OFFLINE 관측 모델.

    Implementation requirements:

    • Presence를 절대 업무 사실로 사용하지 않는다.
  • active connection count와 observed time을 함께 보존한다.

  • 보안·결제 결정을 presence에 의존하지 않는다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.presence;
    
    

class WebSocketPresenceSummaryTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketPresenceSummary.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("actorFingerprint", "activeConnectionCount", "state", "lastObservedAt"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-presence-redis:test --tests 'io.backend.skeleton.websocket.presence.WebSocketPresenceSummaryTest'
```

Expected: FAIL because `WebSocketPresenceSummary` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.presence;

public record WebSocketPresenceSummary( String actorFingerprint, int activeConnectionCount, String state, java.time.Instant lastObservedAt) { public WebSocketPresenceSummary { java.util.Objects.requireNonNull(actorFingerprint, "actorFingerprint"); java.util.Objects.requireNonNull(state, "state"); java.util.Objects.requireNonNull(lastObservedAt, "lastObservedAt"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-presence-redis:test --tests 'io.backend.skeleton.websocket.presence.WebSocketPresenceSummaryTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-presence-redis/src/main/java/io/backend/skeleton/websocket/presence/WebSocketPresenceSummary.java' 'modules/websocket-advanced/websocket-presence-redis/src/test/java/io/backend/skeleton/websocket/presence/WebSocketPresenceSummaryTest.java' 'modules/websocket-advanced/websocket-presence-redis/src/main/java/io/backend/skeleton/websocket/presence/WebSocketPresenceStore.java'
git commit -m "feat: presence-summary"
```

Task 9: STOMP 1.2 Protocol Adapter

**Files:**
- Create: `modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompProfile.java`
  • Create: modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompConfiguration.java

  • Create: modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompDestinationCatalog.java

  • Test: modules/websocket-advanced/websocket-stomp/src/test/java/io/backend/skeleton/websocket/stomp/WebSocketStompProfileTest.java

    Interfaces:

    • Consumes: Stable connection runtime와 Spring Messaging.
    • Produces: STOMP destination catalog와 protocol adapter.

    Implementation requirements:

    • Destination 문자열을 reliability 보장으로 해석하지 않는다.
  • application/broker/user prefix를 분리한다.

  • 임의 SimpMessagingTemplate 사용을 application에서 금지한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.stomp;
    
    

class WebSocketStompProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketStompProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("applicationPrefixes", "brokerPrefixes", "userPrefixes", "preserveOrder"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-stomp:test --tests 'io.backend.skeleton.websocket.stomp.WebSocketStompProfileTest'
```

Expected: FAIL because `WebSocketStompProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.stomp;

public record WebSocketStompProfile( java.util.Set applicationPrefixes, java.util.Set brokerPrefixes, java.util.Set userPrefixes, boolean preserveOrder) { public WebSocketStompProfile { java.util.Objects.requireNonNull(applicationPrefixes, "applicationPrefixes"); java.util.Objects.requireNonNull(brokerPrefixes, "brokerPrefixes"); java.util.Objects.requireNonNull(userPrefixes, "userPrefixes"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-stomp:test --tests 'io.backend.skeleton.websocket.stomp.WebSocketStompProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompProfile.java' 'modules/websocket-advanced/websocket-stomp/src/test/java/io/backend/skeleton/websocket/stomp/WebSocketStompProfileTest.java' 'modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompConfiguration.java' 'modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompDestinationCatalog.java'
git commit -m "feat: stomp-1-2-protocol-adapter"
```

Task 10: STOMP Security·Receipt·ACK Evidence

**Files:**
- Create: `modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompEvidence.java`
  • Create: modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompSecurityInterceptor.java

  • Create: modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompAckPolicy.java

  • Test: modules/websocket-advanced/websocket-stomp/src/test/java/io/backend/skeleton/websocket/stomp/WebSocketStompEvidenceTest.java

    Interfaces:

    • Consumes: STOMP adapter와 Stable evidence model.
    • Produces: RECEIPT·ACK·commit을 분리한 evidence mapping.

    Implementation requirements:

    • RECEIPT을 transaction commit으로 승격하지 않는다.
  • ack:auto·client·client-individual을 구분한다.

  • MESSAGE와 SUBSCRIBE destination authorization을 분리한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.stomp;
    
    

class WebSocketStompEvidenceTest { @org.junit.jupiter.api.Test void valuesAreStable() { org.assertj.core.api.Assertions.assertThat(WebSocketStompEvidence.values()) .extracting(java.lang.Enum::name) .containsExactly("FRAME_RECEIVED", "PROTOCOL_RECEIPT", "BROKER_DELIVERY", "BROKER_ACK", "APPLICATION_COMMIT", "CLIENT_APPLIED"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-stomp:test --tests 'io.backend.skeleton.websocket.stomp.WebSocketStompEvidenceTest'
```

Expected: FAIL because `WebSocketStompEvidence` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.stomp;

public enum WebSocketStompEvidence { FRAME_RECEIVED, PROTOCOL_RECEIPT, BROKER_DELIVERY, BROKER_ACK, APPLICATION_COMMIT, CLIENT_APPLIED } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-stomp:test --tests 'io.backend.skeleton.websocket.stomp.WebSocketStompEvidenceTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompEvidence.java' 'modules/websocket-advanced/websocket-stomp/src/test/java/io/backend/skeleton/websocket/stomp/WebSocketStompEvidenceTest.java' 'modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompSecurityInterceptor.java' 'modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketStompAckPolicy.java'
git commit -m "feat: stomp-security-receipt-ack-evidence"
```

Task 11: Simple Broker Local/Test Profile

**Files:**
- Create: `modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketSimpleBrokerProfile.java`
  • Create: modules/websocket-advanced/websocket-stomp/src/test/java/io/backend/skeleton/websocket/stomp/WebSocketSimpleBrokerContractTest.java

  • Test: modules/websocket-advanced/websocket-stomp/src/test/java/io/backend/skeleton/websocket/stomp/WebSocketSimpleBrokerProfileTest.java

    Interfaces:

    • Consumes: STOMP configuration.
    • Produces: Simple Broker의 단일 node·제한 기능 명시.

    Implementation requirements:

    • clusterSupported와 durableAckSupported는 false다.
  • Local/Test profile 밖에서 activation을 거부한다.

  • ACK·Receipt limitation을 support matrix에 기록한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.stomp;
    
    

class WebSocketSimpleBrokerProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketSimpleBrokerProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("localOnly", "clusterSupported", "durableAckSupported"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-stomp:test --tests 'io.backend.skeleton.websocket.stomp.WebSocketSimpleBrokerProfileTest'
```

Expected: FAIL because `WebSocketSimpleBrokerProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.stomp;

public record WebSocketSimpleBrokerProfile( boolean localOnly, boolean clusterSupported, boolean durableAckSupported) { public WebSocketSimpleBrokerProfile {

}

} ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-stomp:test --tests 'io.backend.skeleton.websocket.stomp.WebSocketSimpleBrokerProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-stomp/src/main/java/io/backend/skeleton/websocket/stomp/WebSocketSimpleBrokerProfile.java' 'modules/websocket-advanced/websocket-stomp/src/test/java/io/backend/skeleton/websocket/stomp/WebSocketSimpleBrokerProfileTest.java' 'modules/websocket-advanced/websocket-stomp/src/test/java/io/backend/skeleton/websocket/stomp/WebSocketSimpleBrokerContractTest.java'
git commit -m "feat: simple-broker-local-test-profile"
```

Task 12: RabbitMQ STOMP Broker Relay

**Files:**
- Create: `modules/websocket-advanced/websocket-broker-relay-rabbit/src/main/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketRabbitBrokerRelayProfile.java`
  • Create: modules/websocket-advanced/websocket-broker-relay-rabbit/src/main/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketRabbitBrokerRelayConfiguration.java

  • Create: modules/websocket-advanced/websocket-broker-relay-rabbit/src/test/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketRabbitBrokerRelayContractTest.java

  • Test: modules/websocket-advanced/websocket-broker-relay-rabbit/src/test/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketRabbitBrokerRelayProfileTest.java

    Interfaces:

    • Consumes: STOMP adapter와 RabbitMQ broker capability.
    • Produces: external relay connection·heartbeat·outage contract.

    Implementation requirements:

    • client connection별 broker connection 비용을 관측한다.
  • broker outage와 reconnect에서 session behavior를 검증한다.

  • destination durability는 Rabbit topology capability에서만 선언한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.stomp.rabbit;
    
    

class WebSocketRabbitBrokerRelayProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketRabbitBrokerRelayProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("host", "port", "tls", "systemHeartbeat"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-broker-relay-rabbit:test --tests 'io.backend.skeleton.websocket.stomp.rabbit.WebSocketRabbitBrokerRelayProfileTest'
```

Expected: FAIL because `WebSocketRabbitBrokerRelayProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.stomp.rabbit;

public record WebSocketRabbitBrokerRelayProfile( String host, int port, boolean tls, java.time.Duration systemHeartbeat) { public WebSocketRabbitBrokerRelayProfile { java.util.Objects.requireNonNull(host, "host"); java.util.Objects.requireNonNull(systemHeartbeat, "systemHeartbeat"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-broker-relay-rabbit:test --tests 'io.backend.skeleton.websocket.stomp.rabbit.WebSocketRabbitBrokerRelayProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-broker-relay-rabbit/src/main/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketRabbitBrokerRelayProfile.java' 'modules/websocket-advanced/websocket-broker-relay-rabbit/src/test/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketRabbitBrokerRelayProfileTest.java' 'modules/websocket-advanced/websocket-broker-relay-rabbit/src/main/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketRabbitBrokerRelayConfiguration.java' 'modules/websocket-advanced/websocket-broker-relay-rabbit/src/test/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketRabbitBrokerRelayContractTest.java'
git commit -m "feat: rabbitmq-stomp-broker-relay"
```

Task 13: Multi-node User Destination

**Files:**
- Create: `modules/websocket-advanced/websocket-broker-relay-rabbit/src/main/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketUserDestinationPolicy.java`
  • Create: modules/websocket-advanced/websocket-broker-relay-rabbit/src/main/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketMultiNodeUserDestination.java

  • Test: modules/websocket-advanced/websocket-broker-relay-rabbit/src/test/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketUserDestinationPolicyTest.java

    Interfaces:

    • Consumes: External session index와 Rabbit relay.
    • Produces: 다른 node에 연결된 user session resolution.

    Implementation requirements:

    • user destination 원문을 metric tag로 사용하지 않는다.
  • unresolved broadcast loop를 방지한다.

  • broker temporary queue cleanup을 검증한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.stomp.rabbit;
    
    

class WebSocketUserDestinationPolicyTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketUserDestinationPolicy.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("broadcastDestination", "unresolvedDestination", "registryTtl"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-broker-relay-rabbit:test --tests 'io.backend.skeleton.websocket.stomp.rabbit.WebSocketUserDestinationPolicyTest'
```

Expected: FAIL because `WebSocketUserDestinationPolicy` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.stomp.rabbit;

public record WebSocketUserDestinationPolicy( String broadcastDestination, String unresolvedDestination, java.time.Duration registryTtl) { public WebSocketUserDestinationPolicy { java.util.Objects.requireNonNull(broadcastDestination, "broadcastDestination"); java.util.Objects.requireNonNull(unresolvedDestination, "unresolvedDestination"); java.util.Objects.requireNonNull(registryTtl, "registryTtl"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-broker-relay-rabbit:test --tests 'io.backend.skeleton.websocket.stomp.rabbit.WebSocketUserDestinationPolicyTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-broker-relay-rabbit/src/main/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketUserDestinationPolicy.java' 'modules/websocket-advanced/websocket-broker-relay-rabbit/src/test/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketUserDestinationPolicyTest.java' 'modules/websocket-advanced/websocket-broker-relay-rabbit/src/main/java/io/backend/skeleton/websocket/stomp/rabbit/WebSocketMultiNodeUserDestination.java'
git commit -m "feat: multi-node-user-destination"
```

Task 14: Protobuf Binary Codec

**Files:**
- Create: `modules/websocket-advanced/websocket-protobuf/src/main/java/io/backend/skeleton/websocket/codec/protobuf/WebSocketProtobufCodecProfile.java`
  • Create: modules/websocket-advanced/websocket-protobuf/src/main/java/io/backend/skeleton/websocket/codec/protobuf/WebSocketProtobufCodec.java

  • Test: modules/websocket-advanced/websocket-protobuf/src/test/java/io/backend/skeleton/websocket/codec/protobuf/WebSocketProtobufCodecProfileTest.java

    Interfaces:

    • Consumes: Stable message catalog와 generated Protobuf descriptors.
    • Produces: versioned binary protocol codec.

    Implementation requirements:

    • Stable JSON과 같은 message type/version 의미를 유지한다.
  • descriptor compatibility gate를 요구한다.

  • 대형 file bytes를 protobuf payload로 허용하지 않는다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.codec.protobuf;
    
    

class WebSocketProtobufCodecProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketProtobufCodecProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("descriptorArtifact", "maxMessageBytes", "unknownFieldAllowed"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-protobuf:test --tests 'io.backend.skeleton.websocket.codec.protobuf.WebSocketProtobufCodecProfileTest'
```

Expected: FAIL because `WebSocketProtobufCodecProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.codec.protobuf;

public record WebSocketProtobufCodecProfile( String descriptorArtifact, int maxMessageBytes, boolean unknownFieldAllowed) { public WebSocketProtobufCodecProfile { java.util.Objects.requireNonNull(descriptorArtifact, "descriptorArtifact"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-protobuf:test --tests 'io.backend.skeleton.websocket.codec.protobuf.WebSocketProtobufCodecProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-protobuf/src/main/java/io/backend/skeleton/websocket/codec/protobuf/WebSocketProtobufCodecProfile.java' 'modules/websocket-advanced/websocket-protobuf/src/test/java/io/backend/skeleton/websocket/codec/protobuf/WebSocketProtobufCodecProfileTest.java' 'modules/websocket-advanced/websocket-protobuf/src/main/java/io/backend/skeleton/websocket/codec/protobuf/WebSocketProtobufCodec.java'
git commit -m "feat: protobuf-binary-codec"
```

Task 15: CBOR Binary Codec

**Files:**
- Create: `modules/websocket-advanced/websocket-cbor/src/main/java/io/backend/skeleton/websocket/codec/cbor/WebSocketCborCodecProfile.java`
  • Create: modules/websocket-advanced/websocket-cbor/src/main/java/io/backend/skeleton/websocket/codec/cbor/WebSocketCborCodec.java

  • Test: modules/websocket-advanced/websocket-cbor/src/test/java/io/backend/skeleton/websocket/codec/cbor/WebSocketCborCodecProfileTest.java

    Interfaces:

    • Consumes: Stable message catalog와 wire type manifest.
    • Produces: bounded CBOR codec profile.

    Implementation requirements:

    • 실제 client demand가 있을 때만 활성화한다.
  • canonical encoding과 duplicate map key 정책을 고정한다.

  • JSON과 semantic schema parity test를 수행한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.codec.cbor;
    
    

class WebSocketCborCodecProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketCborCodecProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("maxMessageBytes", "canonicalEncodingRequired", "unknownFieldAllowed"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cbor:test --tests 'io.backend.skeleton.websocket.codec.cbor.WebSocketCborCodecProfileTest'
```

Expected: FAIL because `WebSocketCborCodecProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.codec.cbor;

public record WebSocketCborCodecProfile( int maxMessageBytes, boolean canonicalEncodingRequired, boolean unknownFieldAllowed) { public WebSocketCborCodecProfile {

}

} ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-cbor:test --tests 'io.backend.skeleton.websocket.codec.cbor.WebSocketCborCodecProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-cbor/src/main/java/io/backend/skeleton/websocket/codec/cbor/WebSocketCborCodecProfile.java' 'modules/websocket-advanced/websocket-cbor/src/test/java/io/backend/skeleton/websocket/codec/cbor/WebSocketCborCodecProfileTest.java' 'modules/websocket-advanced/websocket-cbor/src/main/java/io/backend/skeleton/websocket/codec/cbor/WebSocketCborCodec.java'
git commit -m "feat: cbor-binary-codec"
```

Task 16: permessage-deflate Endpoint Opt-in

**Files:**
- Create: `modules/websocket-advanced/websocket-compression/src/main/java/io/backend/skeleton/websocket/compression/WebSocketCompressionProfile.java`
  • Create: modules/websocket-advanced/websocket-compression/src/main/java/io/backend/skeleton/websocket/compression/WebSocketCompressionPolicy.java

  • Test: modules/websocket-advanced/websocket-compression/src/test/java/io/backend/skeleton/websocket/compression/WebSocketCompressionProfileTest.java

    Interfaces:

    • Consumes: Endpoint profile와 runtime extension negotiation.
    • Produces: RFC 7692 memory·CPU bounded compression policy.

    Implementation requirements:

    • 기본 enabled=false다.
  • decompressed size limit를 별도로 적용한다.

  • sensitive data와 attacker-controlled input 혼합 endpoint를 거부한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.compression;
    
    

class WebSocketCompressionProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketCompressionProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("enabled", "serverNoContextTakeover", "clientNoContextTakeover", "maxWindowBits", "minCompressBytes"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-compression:test --tests 'io.backend.skeleton.websocket.compression.WebSocketCompressionProfileTest'
```

Expected: FAIL because `WebSocketCompressionProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.compression;

public record WebSocketCompressionProfile( boolean enabled, boolean serverNoContextTakeover, boolean clientNoContextTakeover, int maxWindowBits, int minCompressBytes) { public WebSocketCompressionProfile {

}

} ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-compression:test --tests 'io.backend.skeleton.websocket.compression.WebSocketCompressionProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-compression/src/main/java/io/backend/skeleton/websocket/compression/WebSocketCompressionProfile.java' 'modules/websocket-advanced/websocket-compression/src/test/java/io/backend/skeleton/websocket/compression/WebSocketCompressionProfileTest.java' 'modules/websocket-advanced/websocket-compression/src/main/java/io/backend/skeleton/websocket/compression/WebSocketCompressionPolicy.java'
git commit -m "feat: permessage-deflate-endpoint-opt-in"
```

Task 17: Outbound WebSocket Client Profile

**Files:**
- Create: `modules/websocket-advanced/websocket-outbound-client/src/main/java/io/backend/skeleton/websocket/client/NamedWebSocketClientProfile.java`
  • Create: modules/websocket-advanced/websocket-outbound-client/src/main/java/io/backend/skeleton/websocket/client/NamedWebSocketClientRegistry.java

  • Create: modules/websocket-advanced/websocket-outbound-client/src/main/java/io/backend/skeleton/websocket/client/WebSocketClientReconnectPolicy.java

  • Test: modules/websocket-advanced/websocket-outbound-client/src/test/java/io/backend/skeleton/websocket/client/NamedWebSocketClientProfileTest.java

    Interfaces:

    • Consumes: Stable protocol·security·heartbeat contracts.
    • Produces: named outbound connection·TLS·reconnect·resume profile.

    Implementation requirements:

    • 일반 HTTP Client retry를 그대로 사용하지 않는다.
  • endpoint·TLS·subprotocol을 profile에 고정한다.

  • reconnect는 backoff+jitter와 bounded attempts를 사용한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.client;
    
    

class NamedWebSocketClientProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(NamedWebSocketClientProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("name", "uri", "subprotocol", "connectTimeout", "idleTimeout"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-outbound-client:test --tests 'io.backend.skeleton.websocket.client.NamedWebSocketClientProfileTest'
```

Expected: FAIL because `NamedWebSocketClientProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.client;

public record NamedWebSocketClientProfile( String name, String uri, String subprotocol, java.time.Duration connectTimeout, java.time.Duration idleTimeout) { public NamedWebSocketClientProfile { java.util.Objects.requireNonNull(name, "name"); java.util.Objects.requireNonNull(uri, "uri"); java.util.Objects.requireNonNull(subprotocol, "subprotocol"); java.util.Objects.requireNonNull(connectTimeout, "connectTimeout"); java.util.Objects.requireNonNull(idleTimeout, "idleTimeout"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-outbound-client:test --tests 'io.backend.skeleton.websocket.client.NamedWebSocketClientProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-outbound-client/src/main/java/io/backend/skeleton/websocket/client/NamedWebSocketClientProfile.java' 'modules/websocket-advanced/websocket-outbound-client/src/test/java/io/backend/skeleton/websocket/client/NamedWebSocketClientProfileTest.java' 'modules/websocket-advanced/websocket-outbound-client/src/main/java/io/backend/skeleton/websocket/client/NamedWebSocketClientRegistry.java' 'modules/websocket-advanced/websocket-outbound-client/src/main/java/io/backend/skeleton/websocket/client/WebSocketClientReconnectPolicy.java'
git commit -m "feat: outbound-websocket-client-profile"
```

Task 18: SockJS Legacy Compatibility

**Files:**
- Create: `modules/websocket-advanced/websocket-sockjs-compat/src/main/java/io/backend/skeleton/websocket/sockjs/WebSocketSockJsCompatibilityProfile.java`
  • Create: modules/websocket-advanced/websocket-sockjs-compat/src/main/java/io/backend/skeleton/websocket/sockjs/WebSocketSockJsConfiguration.java

  • Test: modules/websocket-advanced/websocket-sockjs-compat/src/test/java/io/backend/skeleton/websocket/sockjs/WebSocketSockJsCompatibilityProfileTest.java

    Interfaces:

    • Consumes: STOMP 또는 Raw compatibility endpoint.
    • Produces: legacy browser transport fallback profile.

    Implementation requirements:

    • 신규 서비스 기본에서 비활성이다.
  • Origin·CSRF·session budget을 Stable보다 완화하지 않는다.

  • fallback transport별 proxy/cache behavior를 시험한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.sockjs;
    
    

class WebSocketSockJsCompatibilityProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketSockJsCompatibilityProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("enabled", "transports", "sessionCookieLifetime"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-sockjs-compat:test --tests 'io.backend.skeleton.websocket.sockjs.WebSocketSockJsCompatibilityProfileTest'
```

Expected: FAIL because `WebSocketSockJsCompatibilityProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.sockjs;

public record WebSocketSockJsCompatibilityProfile( boolean enabled, java.util.Set transports, java.time.Duration sessionCookieLifetime) { public WebSocketSockJsCompatibilityProfile { java.util.Objects.requireNonNull(transports, "transports"); java.util.Objects.requireNonNull(sessionCookieLifetime, "sessionCookieLifetime"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-sockjs-compat:test --tests 'io.backend.skeleton.websocket.sockjs.WebSocketSockJsCompatibilityProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-sockjs-compat/src/main/java/io/backend/skeleton/websocket/sockjs/WebSocketSockJsCompatibilityProfile.java' 'modules/websocket-advanced/websocket-sockjs-compat/src/test/java/io/backend/skeleton/websocket/sockjs/WebSocketSockJsCompatibilityProfileTest.java' 'modules/websocket-advanced/websocket-sockjs-compat/src/main/java/io/backend/skeleton/websocket/sockjs/WebSocketSockJsConfiguration.java'
git commit -m "feat: sockjs-legacy-compatibility"
```

Task 19: HTTP/2 Extended CONNECT Compatibility

**Files:**
- Create: `modules/websocket-advanced/websocket-http2-compat/src/main/java/io/backend/skeleton/websocket/http2/WebSocketHttp2CompatibilityProfile.java`
  • Create: modules/websocket-advanced/websocket-http2-compat/src/test/java/io/backend/skeleton/websocket/http2/WebSocketHttp2EndToEndContractTest.java

  • Test: modules/websocket-advanced/websocket-http2-compat/src/test/java/io/backend/skeleton/websocket/http2/WebSocketHttp2CompatibilityProfileTest.java

    Interfaces:

    • Consumes: Stable runtime과 RFC 8441 capable path.
    • Produces: ClientNginx/IngressRuntime E2E evidence.

    Implementation requirements:

    • 표준 존재와 플랫폼 지원을 구분한다.
  • 모든 hop이 Extended CONNECT를 지원할 때만 enable한다.

  • fallback classic Upgrade behavior를 검증한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.http2;
    
    

class WebSocketHttp2CompatibilityProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketHttp2CompatibilityProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("enabled", "validatedClients", "validatedProxies"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-http2-compat:test --tests 'io.backend.skeleton.websocket.http2.WebSocketHttp2CompatibilityProfileTest'
```

Expected: FAIL because `WebSocketHttp2CompatibilityProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.http2;

public record WebSocketHttp2CompatibilityProfile( boolean enabled, java.util.Set validatedClients, java.util.Set validatedProxies) { public WebSocketHttp2CompatibilityProfile { java.util.Objects.requireNonNull(validatedClients, "validatedClients"); java.util.Objects.requireNonNull(validatedProxies, "validatedProxies"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-http2-compat:test --tests 'io.backend.skeleton.websocket.http2.WebSocketHttp2CompatibilityProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-http2-compat/src/main/java/io/backend/skeleton/websocket/http2/WebSocketHttp2CompatibilityProfile.java' 'modules/websocket-advanced/websocket-http2-compat/src/test/java/io/backend/skeleton/websocket/http2/WebSocketHttp2CompatibilityProfileTest.java' 'modules/websocket-advanced/websocket-http2-compat/src/test/java/io/backend/skeleton/websocket/http2/WebSocketHttp2EndToEndContractTest.java'
git commit -m "feat: http-2-extended-connect-compatibility"
```

Task 20: HTTP/3 WebSocket Experimental

**Files:**
- Create: `modules/websocket-advanced/websocket-http3-experimental/src/main/java/io/backend/skeleton/websocket/http3/WebSocketHttp3ExperimentalProfile.java`
  • Create: modules/websocket-advanced/websocket-http3-experimental/src/test/java/io/backend/skeleton/websocket/http3/WebSocketHttp3ExperimentalContractTest.java

  • Test: modules/websocket-advanced/websocket-http3-experimental/src/test/java/io/backend/skeleton/websocket/http3/WebSocketHttp3ExperimentalProfileTest.java

    Interfaces:

    • Consumes: RFC 9220 capable client/proxy/runtime.
    • Produces: Experimental E2E compatibility evidence only.

    Implementation requirements:

    • Stable support로 광고하지 않는다.
  • QUIC·proxy·browser matrix를 별도로 기록한다.

  • fallback·rollback path가 없으면 production promotion을 금지한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.http3;
    
    

class WebSocketHttp3ExperimentalProfileTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketHttp3ExperimentalProfile.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("enabled", "quicImplementation", "validatedClients"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-http3-experimental:test --tests 'io.backend.skeleton.websocket.http3.WebSocketHttp3ExperimentalProfileTest'
```

Expected: FAIL because `WebSocketHttp3ExperimentalProfile` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.http3;

public record WebSocketHttp3ExperimentalProfile( boolean enabled, String quicImplementation, java.util.Set validatedClients) { public WebSocketHttp3ExperimentalProfile { java.util.Objects.requireNonNull(quicImplementation, "quicImplementation"); java.util.Objects.requireNonNull(validatedClients, "validatedClients"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-http3-experimental:test --tests 'io.backend.skeleton.websocket.http3.WebSocketHttp3ExperimentalProfileTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-http3-experimental/src/main/java/io/backend/skeleton/websocket/http3/WebSocketHttp3ExperimentalProfile.java' 'modules/websocket-advanced/websocket-http3-experimental/src/test/java/io/backend/skeleton/websocket/http3/WebSocketHttp3ExperimentalProfileTest.java' 'modules/websocket-advanced/websocket-http3-experimental/src/test/java/io/backend/skeleton/websocket/http3/WebSocketHttp3ExperimentalContractTest.java'
git commit -m "feat: http-3-websocket-experimental"
```

Task 21: GraphQL WebSocket Transport Bridge

**Files:**
- Create: `modules/websocket-advanced/websocket-graphql-transport-bridge/src/main/java/io/backend/skeleton/websocket/graphql/WebSocketGraphQlTransportBridgePolicy.java`
  • Create: modules/websocket-advanced/websocket-graphql-transport-bridge/src/main/java/io/backend/skeleton/websocket/graphql/WebSocketGraphQlTransportBridge.java

  • Test: modules/websocket-advanced/websocket-graphql-transport-bridge/src/test/java/io/backend/skeleton/websocket/graphql/WebSocketGraphQlTransportBridgePolicyTest.java

    Interfaces:

    • Consumes: Stable Connection Runtime와 GraphQL Platform subscription transport SPI.
    • Produces: graphql-transport-ws transport bridge without schema/operation semantics.

    Implementation requirements:

    • ownsGraphQlSemantics=false다.
  • GraphQL error·operation·subscription lifecycle을 재구현하지 않는다.

  • Stable security·queue·heartbeat budget을 재사용한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.graphql;
    
    

class WebSocketGraphQlTransportBridgePolicyTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketGraphQlTransportBridgePolicy.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("subprotocol", "ownsGraphQlSemantics", "usesStableConnectionRuntime"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-graphql-transport-bridge:test --tests 'io.backend.skeleton.websocket.graphql.WebSocketGraphQlTransportBridgePolicyTest'
```

Expected: FAIL because `WebSocketGraphQlTransportBridgePolicy` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.graphql;

public record WebSocketGraphQlTransportBridgePolicy( String subprotocol, boolean ownsGraphQlSemantics, boolean usesStableConnectionRuntime) { public WebSocketGraphQlTransportBridgePolicy { java.util.Objects.requireNonNull(subprotocol, "subprotocol"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-graphql-transport-bridge:test --tests 'io.backend.skeleton.websocket.graphql.WebSocketGraphQlTransportBridgePolicyTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-graphql-transport-bridge/src/main/java/io/backend/skeleton/websocket/graphql/WebSocketGraphQlTransportBridgePolicy.java' 'modules/websocket-advanced/websocket-graphql-transport-bridge/src/test/java/io/backend/skeleton/websocket/graphql/WebSocketGraphQlTransportBridgePolicyTest.java' 'modules/websocket-advanced/websocket-graphql-transport-bridge/src/main/java/io/backend/skeleton/websocket/graphql/WebSocketGraphQlTransportBridge.java'
git commit -m "feat: graphql-websocket-transport-bridge"
```

Task 22: Advanced Promotion·Soak·Rollback Gate

**Files:**
- Create: `modules/websocket-advanced/websocket-advanced-bootstrap/src/main/java/io/backend/skeleton/websocket/release/WebSocketAdvancedPromotionGate.java`
  • Create: docs/superpowers/adr/ADR-WS-002-resume-and-cluster.md

  • Create: docs/superpowers/adr/ADR-WS-003-stomp-and-broker-relay.md

  • Create: docs/superpowers/runbooks/websocket-broker-outage.md

  • Create: docs/superpowers/runbooks/websocket-resume-history-loss.md

  • Create: docs/superpowers/support/websocket-advanced-support-matrix.md

  • Test: modules/websocket-advanced/websocket-advanced-bootstrap/src/test/java/io/backend/skeleton/websocket/release/WebSocketAdvancedPromotionGateTest.java

    Interfaces:

    • Consumes: Advanced Task 121과 Stable Release Gate.
    • Produces: feature별 promotion evidence와 rollback runbook.

    Implementation requirements:

    • Resume·Cluster·STOMP·Compression·H2/H3를 각각 독립 승격한다.
  • Stable artifact dependency graph와 wire contract가 바뀌지 않았음을 검증한다.

  • 실제 multi-node·broker·browser soak와 rollback을 요구한다.

    • Step 1: Write the failing test
    package io.backend.skeleton.websocket.release;
    
    

class WebSocketAdvancedPromotionGateTest { @org.junit.jupiter.api.Test void recordShapeIsStable() { var names = java.util.Arrays.stream(WebSocketAdvancedPromotionGate.class.getRecordComponents()) .map(java.lang.reflect.RecordComponent::getName) .toList(); org.assertj.core.api.Assertions.assertThat(names) .containsExactly("requiredSuites", "minimumSoak", "rollbackValidated", "stableArtifactUnchanged"); } } ```

- [ ] **Step 2: Run the focused test and verify the failure**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-advanced-bootstrap:test --tests 'io.backend.skeleton.websocket.release.WebSocketAdvancedPromotionGateTest'
```

Expected: FAIL because `WebSocketAdvancedPromotionGate` and its required policy contract do not exist yet.

- [ ] **Step 3: Implement the smallest complete production contract**

```java
package io.backend.skeleton.websocket.release;

public record WebSocketAdvancedPromotionGate( java.util.Set requiredSuites, java.time.Duration minimumSoak, boolean rollbackValidated, boolean stableArtifactUnchanged) { public WebSocketAdvancedPromotionGate { java.util.Objects.requireNonNull(requiredSuites, "requiredSuites"); java.util.Objects.requireNonNull(minimumSoak, "minimumSoak"); } } ```

Implement every listed production file with the exact public names, boundaries and invariants above. Keep raw transport sessions, credentials, payloads, database types and dynamic identifiers out of the public contract unless this task explicitly defines a bounded adapter type.

- [ ] **Step 4: Run the focused test and the owning suite**

Run:

```bash
./gradlew :modules:websocket-advanced:websocket-advanced-bootstrap:test --tests 'io.backend.skeleton.websocket.release.WebSocketAdvancedPromotionGateTest'
./gradlew websocketAdvancedTest
```

Expected: PASS for the focused test and the aggregate Advanced suite.

- [ ] **Step 5: Commit the independently reviewable change**

```bash
git add 'modules/websocket-advanced/websocket-advanced-bootstrap/src/main/java/io/backend/skeleton/websocket/release/WebSocketAdvancedPromotionGate.java' 'modules/websocket-advanced/websocket-advanced-bootstrap/src/test/java/io/backend/skeleton/websocket/release/WebSocketAdvancedPromotionGateTest.java' 'docs/superpowers/adr/ADR-WS-002-resume-and-cluster.md' 'docs/superpowers/adr/ADR-WS-003-stomp-and-broker-relay.md' 'docs/superpowers/runbooks/websocket-broker-outage.md' 'docs/superpowers/runbooks/websocket-resume-history-loss.md' 'docs/superpowers/support/websocket-advanced-support-matrix.md'
git commit -m "feat: advanced-promotion-soak-rollback-gate"
```