1738 lines
79 KiB
Markdown
1738 lines
79 KiB
Markdown
# 타입 안전 gRPC 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 Unary·Server Streaming 플랫폼을 변경하지 않고 Edition 2024/2026, Client/Bidirectional Streaming, Manual Flow Control, Hedging, Custom Resolver·Load Balancer, xDS, gRPC-Web, Servlet, Spring Integration, Reactor·Kotlin과 Channel Diagnostics를 독립적으로 검증·승격할 수 있는 Advanced capability 계층을 구축한다.
|
||
|
||
**Architecture:** 모든 Advanced 기능은 `modules/grpc-advanced`에 격리하고 명시적 feature flag를 요구한다. Stable method policy, deadline, execution evidence, security, status, metadata, observability와 streaming guardrail을 그대로 소비하며 raw gRPC escape hatch를 제공하지 않는다. 각 capability는 자체 Compatibility·Security·Fault·Performance·Soak gate를 통과한 뒤에만 Advanced Stable로 승격된다.
|
||
|
||
**Tech Stack:** Java 21, Spring Boot 4.1 BOM, Boot-managed gRPC Java/Protobuf, Protobuf Editions, grpc-xds, grpc-web proxy/Envoy, Servlet HTTP/2, Spring Integration 7.1, Project Reactor, Kotlin coroutines/Flow, Channelz/CSDS, JUnit 5, AssertJ, Testcontainers, Toxiproxy.
|
||
|
||
## Global Constraints
|
||
|
||
- Stable Task 1–53과 Stable Release Gate가 먼저 완료돼야 한다.
|
||
- Advanced module root는 `modules/grpc-advanced`이다.
|
||
- Root package는 `io.backend.skeleton.grpc.advanced`이다.
|
||
- 모든 capability는 `backend.grpc.advanced.*` 아래의 명시적 feature flag를 요구한다.
|
||
- Stable starter는 Advanced module에 compile/runtime dependency를 갖지 않는다.
|
||
- Edition 2024는 opt-in이고 Edition 2026은 Watch/Experimental이다.
|
||
- Client Streaming과 Bidirectional Streaming은 session·sequence·dedup·resume 계약 없이 활성화하지 않는다.
|
||
- Manual Flow Control이 raw StreamObserver를 application에 직접 반환해서는 안 된다.
|
||
- Hedging은 READ_ONLY Unary만 허용한다.
|
||
- Custom Resolver와 Load Balancer는 Stable channel security·retry owner·observability를 우회하지 않는다.
|
||
- xDS는 proxyless GR3 Experimental로 유지한다.
|
||
- gRPC-Web은 Unary와 Server Streaming만 지원한다.
|
||
- Client Streaming·Bidirectional Streaming의 gRPC-Web 지원을 선언하지 않는다.
|
||
- Servlet transport는 Netty certification을 대체하지 않는다.
|
||
- Spring Integration bridge는 Generated Stub·Service 타입 안전 계약을 대체하지 않는다.
|
||
- Reactor와 Kotlin adapter는 Stable Core 타입과 execution evidence를 유지한다.
|
||
- Channelz·CSDS는 관리자 전용이며 credential·metadata·payload를 노출하지 않는다.
|
||
- 각 capability는 독립 승격이 가능해야 한다.
|
||
- 모든 task는 red-green TDD와 독립 commit으로 끝난다.
|
||
|
||
---
|
||
|
||
## Execution Baseline
|
||
|
||
```text
|
||
Stable Task 1–53
|
||
→ Advanced Task 1–18
|
||
```
|
||
|
||
## Advanced Module Map
|
||
|
||
```text
|
||
modules/grpc-advanced/
|
||
├── grpc-advanced-bootstrap
|
||
├── grpc-edition-2024
|
||
├── grpc-edition-2026-experimental
|
||
├── grpc-client-streaming
|
||
├── grpc-bidi-streaming
|
||
├── grpc-manual-flow-control
|
||
├── grpc-hedging
|
||
├── grpc-custom-resolver
|
||
├── grpc-custom-load-balancer
|
||
├── grpc-xds
|
||
├── grpc-web
|
||
├── grpc-servlet-compat
|
||
├── grpc-integration-bridge
|
||
├── grpc-reactor
|
||
├── grpc-kotlin
|
||
└── grpc-channel-diagnostics
|
||
```
|
||
|
||
## Capability Classification
|
||
|
||
| Capability | Initial grade | Stable default |
|
||
|---|---|---:|
|
||
| Edition 2024 | Advanced opt-in | No |
|
||
| Edition 2026 | Watch/Experimental | No |
|
||
| Client Streaming | Advanced | No |
|
||
| Bidirectional Streaming | Advanced | No |
|
||
| Manual Flow Control | Advanced | No |
|
||
| Hedging | Experimental | No |
|
||
| Custom Resolver | Advanced | No |
|
||
| Custom Load Balancer | Experimental | No |
|
||
| xDS | Experimental | No |
|
||
| gRPC-Web | Advanced compatibility | No |
|
||
| Servlet HTTP/2 | Compatibility | No |
|
||
| Spring Integration bridge | Optional | No |
|
||
| Reactor adapter | Optional Advanced | No |
|
||
| Kotlin adapter | Optional Advanced | No |
|
||
| Channelz/CSDS diagnostics | Admin Advanced | No |
|
||
|
||
## Delivery Phases
|
||
|
||
| Phase | Tasks | Result |
|
||
|---|---:|---|
|
||
| Boundary·Edition | 1–3 | Feature isolation and schema evaluation lanes |
|
||
| Streaming | 4–7 | Client/Bidi session, checkpoint and manual flow control |
|
||
| Resilience·Discovery | 8–11 | Hedging, custom resolver/LB and xDS |
|
||
| Compatibility Bridges | 12–16 | gRPC-Web, Servlet, Integration, Reactor, Kotlin |
|
||
| Diagnostics·Promotion | 17–18 | Admin diagnostics, infrastructure testkit and promotion gate |
|
||
|
||
---
|
||
### Task 1: Advanced Module Boundary와 Feature Flag
|
||
|
||
**Files:**
|
||
- Modify: `settings.gradle.kts`
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedCapability.java`
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedFeatureFlags.java`
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedModuleGuard.java`
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedCapabilityDisabledException.java`
|
||
- Create: `modules/grpc-advanced/build.gradle.kts`
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/build.gradle.kts`
|
||
- Test: `modules/grpc-advanced/grpc-advanced-bootstrap/src/test/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedModuleGuardTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable Task 53 release evidence와 Spring Boot environment.
|
||
- Produces: Advanced·Experimental capability가 Stable starter에 자동 유입되지 않도록 하는 dependency·feature flag 경계.
|
||
|
||
**Implementation requirements:**
|
||
- 모든 capability는 `backend.grpc.advanced.*` 아래의 명시적 flag를 요구한다.
|
||
- Stable starter는 Advanced module에 compile/runtime dependency를 갖지 않는다.
|
||
- capability grade는 `ADVANCED_STABLE`, `EXPERIMENTAL`, `WATCH`, `DISABLED`로 구분한다.
|
||
- production에서 Experimental capability는 별도 승인 profile 없이는 시작되지 않는다.
|
||
- Advanced module은 Stable public types를 소비하지만 Stable guardrail을 우회하지 않는다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcAdvancedModuleGuardTest {
|
||
@org.junit.jupiter.api.Test
|
||
void disabledCapabilityCannotStart() {
|
||
var guard = new GrpcAdvancedModuleGuard(
|
||
GrpcAdvancedFeatureFlags.disabled());
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> guard.requireEnabled(
|
||
GrpcAdvancedCapability.XDS))
|
||
.isInstanceOf(
|
||
GrpcAdvancedCapabilityDisabledException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-advanced-bootstrap:test --tests 'io.backend.skeleton.grpc.advanced.bootstrap.GrpcAdvancedModuleGuardTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public enum GrpcAdvancedCapability {
|
||
EDITION_2024, EDITION_2026,
|
||
CLIENT_STREAMING, BIDI_STREAMING,
|
||
MANUAL_FLOW_CONTROL, HEDGING,
|
||
CUSTOM_RESOLVER, CUSTOM_LOAD_BALANCER,
|
||
XDS, GRPC_WEB, SERVLET_COMPAT,
|
||
SPRING_INTEGRATION, REACTOR, KOTLIN,
|
||
CHANNEL_DIAGNOSTICS
|
||
}
|
||
|
||
public record GrpcAdvancedFeatureFlags(
|
||
java.util.Set<GrpcAdvancedCapability> enabled) {
|
||
public static GrpcAdvancedFeatureFlags disabled() {
|
||
return new GrpcAdvancedFeatureFlags(java.util.Set.of());
|
||
}
|
||
public boolean isEnabled(GrpcAdvancedCapability capability) {
|
||
return enabled.contains(capability);
|
||
}
|
||
}
|
||
|
||
public final class GrpcAdvancedModuleGuard {
|
||
private final GrpcAdvancedFeatureFlags flags;
|
||
public GrpcAdvancedModuleGuard(
|
||
GrpcAdvancedFeatureFlags flags) {
|
||
this.flags = flags;
|
||
}
|
||
public void requireEnabled(
|
||
GrpcAdvancedCapability capability) {
|
||
if (!flags.isEnabled(capability)) {
|
||
throw new GrpcAdvancedCapabilityDisabledException(
|
||
capability.name());
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-advanced-bootstrap:test --tests 'io.backend.skeleton.grpc.advanced.bootstrap.GrpcAdvancedModuleGuardTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedCapability.java' 'modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedFeatureFlags.java' 'modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedModuleGuard.java' 'modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedCapabilityDisabledException.java' 'modules/grpc-advanced/build.gradle.kts' 'modules/grpc-advanced/grpc-advanced-bootstrap/build.gradle.kts' 'modules/grpc-advanced/grpc-advanced-bootstrap/src/test/java/io/backend/skeleton/grpc/advanced/bootstrap/GrpcAdvancedModuleGuardTest.java' 'settings.gradle.kts'
|
||
git commit -m "build: isolate grpc advanced modules"
|
||
```
|
||
|
||
### Task 2: Protobuf Edition 2024 Opt-in Lane
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-edition-2024/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2024Policy.java`
|
||
- Create: `modules/grpc-advanced/grpc-edition-2024/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEditionCompatibilityReport.java`
|
||
- Create: `modules/grpc-advanced/grpc-edition-2024/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2024Gate.java`
|
||
- Create: `modules/grpc-advanced/grpc-edition-2024/src/main/proto/edition2024/compatibility.proto`
|
||
- Create: `modules/grpc-advanced/grpc-edition-2024/buf.yaml`
|
||
- Test: `modules/grpc-advanced/grpc-edition-2024/src/test/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2024GateTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable proto3+optional descriptor, consumer language/toolchain fixtures와 Buf gate.
|
||
- Produces: Edition 2024 schema를 Stable 기본값과 분리하여 검증·비교하는 Advanced lane.
|
||
|
||
**Implementation requirements:**
|
||
- Edition 2024는 module-level opt-in으로만 사용한다.
|
||
- Java뿐 아니라 지원 consumer language/toolchain compile evidence를 요구한다.
|
||
- proto3+optional과 wire/source/JSON behavior를 비교한다.
|
||
- public service를 Edition 2024로 이동하려면 승격 ADR과 consumer migration이 필요하다.
|
||
- Edition 2024 failure가 Stable proto3 release를 차단하지 않되 promotion을 차단한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcEdition2024GateTest {
|
||
@org.junit.jupiter.api.Test
|
||
void missingConsumerEvidenceBlocksPromotion() {
|
||
var gate = new GrpcEdition2024Gate();
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
gate.promotable(new GrpcEditionCompatibilityReport(
|
||
true, false, true))).isFalse();
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-edition-2024:test --tests 'io.backend.skeleton.grpc.advanced.edition.GrpcEdition2024GateTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcEditionCompatibilityReport(
|
||
boolean javaCompatible,
|
||
boolean consumerLanguagesCompatible,
|
||
boolean jsonCompatible) {}
|
||
|
||
public final class GrpcEdition2024Gate {
|
||
public boolean promotable(
|
||
GrpcEditionCompatibilityReport report) {
|
||
return report.javaCompatible()
|
||
&& report.consumerLanguagesCompatible()
|
||
&& report.jsonCompatible();
|
||
}
|
||
}
|
||
|
||
public record GrpcEdition2024Policy(
|
||
boolean explicitOptIn,
|
||
boolean stableDefault) {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-edition-2024:test --tests 'io.backend.skeleton.grpc.advanced.edition.GrpcEdition2024GateTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-edition-2024/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2024Policy.java' 'modules/grpc-advanced/grpc-edition-2024/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEditionCompatibilityReport.java' 'modules/grpc-advanced/grpc-edition-2024/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2024Gate.java' 'modules/grpc-advanced/grpc-edition-2024/src/main/proto/edition2024/compatibility.proto' 'modules/grpc-advanced/grpc-edition-2024/buf.yaml' 'modules/grpc-advanced/grpc-edition-2024/src/test/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2024GateTest.java'
|
||
git commit -m "test: add protobuf edition 2024 lane"
|
||
```
|
||
|
||
### Task 3: Protobuf Edition 2026 Watch Lane
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-edition-2026-experimental/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2026Status.java`
|
||
- Create: `modules/grpc-advanced/grpc-edition-2026-experimental/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2026WatchReport.java`
|
||
- Create: `modules/grpc-advanced/grpc-edition-2026-experimental/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2026Guard.java`
|
||
- Test: `modules/grpc-advanced/grpc-edition-2026-experimental/src/test/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2026GuardTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 2 Edition 2024 report와 current Protobuf toolchain metadata.
|
||
- Produces: released-edition 상태와 toolchain support가 확정되기 전 Stable 사용을 차단하는 Watch lane.
|
||
|
||
**Implementation requirements:**
|
||
- Edition 2026은 `WATCH` 또는 `EXPERIMENTAL` 상태만 가진다.
|
||
- official release status, protoc support, Buf support, Java/runtime support를 분리 기록한다.
|
||
- Stable public contract source로 사용하지 않는다.
|
||
- CI failure는 watch report를 생성하지만 Stable build와 분리한다.
|
||
- 승격은 별도 ADR와 complete cross-language evidence를 요구한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcEdition2026GuardTest {
|
||
@org.junit.jupiter.api.Test
|
||
void edition2026CannotBeStable() {
|
||
var guard = new GrpcEdition2026Guard();
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> guard.requireStable(
|
||
GrpcEdition2026Status.WATCH))
|
||
.isInstanceOf(IllegalStateException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-edition-2026-experimental:test --tests 'io.backend.skeleton.grpc.advanced.edition.GrpcEdition2026GuardTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public enum GrpcEdition2026Status {
|
||
WATCH, EXPERIMENTAL
|
||
}
|
||
|
||
public final class GrpcEdition2026Guard {
|
||
public void requireStable(
|
||
GrpcEdition2026Status status) {
|
||
throw new IllegalStateException(
|
||
"Edition 2026 is not a Stable contract");
|
||
}
|
||
}
|
||
|
||
public record GrpcEdition2026WatchReport(
|
||
boolean officiallyReleased,
|
||
boolean protocSupported,
|
||
boolean bufSupported,
|
||
boolean javaSupported) {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-edition-2026-experimental:test --tests 'io.backend.skeleton.grpc.advanced.edition.GrpcEdition2026GuardTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-edition-2026-experimental/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2026Status.java' 'modules/grpc-advanced/grpc-edition-2026-experimental/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2026WatchReport.java' 'modules/grpc-advanced/grpc-edition-2026-experimental/src/main/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2026Guard.java' 'modules/grpc-advanced/grpc-edition-2026-experimental/src/test/java/io/backend/skeleton/grpc/advanced/edition/GrpcEdition2026GuardTest.java'
|
||
git commit -m "test: track protobuf edition 2026"
|
||
```
|
||
|
||
### Task 4: Client Streaming Session·Sequence Contract
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamSessionId.java`
|
||
- Create: `modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamMessage.java`
|
||
- Create: `modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamPolicy.java`
|
||
- Create: `modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamState.java`
|
||
- Test: `modules/grpc-advanced/grpc-client-streaming/src/test/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamPolicyTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable method policy, deadline, metadata, execution evidence와 generated client-streaming RPC.
|
||
- Produces: session ID, client sequence, half-close, server result를 명시하는 client-streaming contract.
|
||
|
||
**Implementation requirements:**
|
||
- 각 stream message는 session generation과 monotonic client sequence를 가진다.
|
||
- half-close 이후 새 message를 거부한다.
|
||
- 전체 stream을 transparent retry하지 않는다.
|
||
- server final response 전에 일부 message가 적용됐을 수 있음을 evidence로 보존한다.
|
||
- stream max duration, idle timeout, message rate와 in-flight limit을 요구한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcClientStreamPolicyTest {
|
||
@org.junit.jupiter.api.Test
|
||
void duplicateOrDecreasingSequenceIsRejected() {
|
||
var policy = new GrpcClientStreamPolicy();
|
||
policy.accept(1);
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> policy.accept(1))
|
||
.isInstanceOf(IllegalArgumentException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-client-streaming:test --tests 'io.backend.skeleton.grpc.advanced.streaming.GrpcClientStreamPolicyTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcClientStreamSessionId(String value) {}
|
||
|
||
public record GrpcClientStreamMessage<T>(
|
||
GrpcClientStreamSessionId sessionId,
|
||
long sequence,
|
||
T payload) {}
|
||
|
||
public final class GrpcClientStreamPolicy {
|
||
private long last;
|
||
public void accept(long sequence) {
|
||
if (sequence <= last) {
|
||
throw new IllegalArgumentException(
|
||
"client stream sequence must increase");
|
||
}
|
||
last = sequence;
|
||
}
|
||
}
|
||
|
||
public enum GrpcClientStreamState {
|
||
OPEN, HALF_CLOSED, COMPLETED, CANCELLED, UNKNOWN
|
||
}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-client-streaming:test --tests 'io.backend.skeleton.grpc.advanced.streaming.GrpcClientStreamPolicyTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamSessionId.java' 'modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamMessage.java' 'modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamPolicy.java' 'modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamState.java' 'modules/grpc-advanced/grpc-client-streaming/src/test/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamPolicyTest.java'
|
||
git commit -m "feat: define grpc client streaming sessions"
|
||
```
|
||
|
||
### Task 5: Client Streaming Dedup·Checkpoint·Resume
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamCheckpoint.java`
|
||
- Create: `modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientMessageDeduplicator.java`
|
||
- Create: `modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamResumeDecision.java`
|
||
- Test: `modules/grpc-advanced/grpc-client-streaming/src/test/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientMessageDeduplicatorTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 4 client stream session and application checkpoint storage.
|
||
- Produces: 재연결 시 client message 중복을 제거하고 last applied sequence에서 resume하는 contract.
|
||
|
||
**Implementation requirements:**
|
||
- dedup identity는 stream session + sequence다.
|
||
- checkpoint는 application side effect와 가능한 한 같은 transaction에 저장한다.
|
||
- checkpoint 이전 duplicate는 replay 결과를 반환하거나 무시한다.
|
||
- history·session이 만료되면 새 stream/full resync를 요구한다.
|
||
- transport ACK와 application applied checkpoint를 구분한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcClientMessageDeduplicatorTest {
|
||
@org.junit.jupiter.api.Test
|
||
void alreadyAppliedSequenceIsDuplicate() {
|
||
var deduplicator = new GrpcClientMessageDeduplicator(10);
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
deduplicator.accept(9)).isFalse();
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
deduplicator.accept(11)).isTrue();
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-client-streaming:test --tests 'io.backend.skeleton.grpc.advanced.streaming.GrpcClientMessageDeduplicatorTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcClientStreamCheckpoint(
|
||
String sessionId,
|
||
long lastAppliedSequence,
|
||
String businessRevision) {}
|
||
|
||
public final class GrpcClientMessageDeduplicator {
|
||
private long lastApplied;
|
||
public GrpcClientMessageDeduplicator(long lastApplied) {
|
||
this.lastApplied = lastApplied;
|
||
}
|
||
public boolean accept(long sequence) {
|
||
if (sequence <= lastApplied) {
|
||
return false;
|
||
}
|
||
lastApplied = sequence;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
public enum GrpcClientStreamResumeDecision {
|
||
RESUME, START_NEW, FULL_RESYNC_REQUIRED
|
||
}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-client-streaming:test --tests 'io.backend.skeleton.grpc.advanced.streaming.GrpcClientMessageDeduplicatorTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamCheckpoint.java' 'modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientMessageDeduplicator.java' 'modules/grpc-advanced/grpc-client-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientStreamResumeDecision.java' 'modules/grpc-advanced/grpc-client-streaming/src/test/java/io/backend/skeleton/grpc/advanced/streaming/GrpcClientMessageDeduplicatorTest.java'
|
||
git commit -m "feat: add grpc client stream resume"
|
||
```
|
||
|
||
### Task 6: Bidirectional Streaming Dual Sequence·Lifecycle
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-bidi-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiSession.java`
|
||
- Create: `modules/grpc-advanced/grpc-bidi-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiDirectionState.java`
|
||
- Create: `modules/grpc-advanced/grpc-bidi-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiSequenceTracker.java`
|
||
- Create: `modules/grpc-advanced/grpc-bidi-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiResumeState.java`
|
||
- Test: `modules/grpc-advanced/grpc-bidi-streaming/src/test/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiSequenceTrackerTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 4–5 client stream contract와 Stable server stream envelope.
|
||
- Produces: client→server와 server→client sequence·flow-control·resume를 독립적으로 관리하는 bidi session.
|
||
|
||
**Implementation requirements:**
|
||
- 양 방향 sequence를 하나의 counter로 합치지 않는다.
|
||
- 각 방향의 half-close와 cancellation을 독립적으로 기록한다.
|
||
- 양 방향 bounded queue와 single writer를 사용한다.
|
||
- resume token은 client applied/server applied sequence를 모두 보존한다.
|
||
- session generation mismatch는 full session restart를 요구한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcBidiSequenceTrackerTest {
|
||
@org.junit.jupiter.api.Test
|
||
void directionsAdvanceIndependently() {
|
||
var tracker = new GrpcBidiSequenceTracker();
|
||
tracker.acceptClient(1);
|
||
tracker.acceptServer(1);
|
||
tracker.acceptServer(2);
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
tracker.clientSequence()).isEqualTo(1);
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
tracker.serverSequence()).isEqualTo(2);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-bidi-streaming:test --tests 'io.backend.skeleton.grpc.advanced.streaming.GrpcBidiSequenceTrackerTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public final class GrpcBidiSequenceTracker {
|
||
private long client;
|
||
private long server;
|
||
public void acceptClient(long sequence) {
|
||
if (sequence <= client) {
|
||
throw new IllegalArgumentException("client sequence");
|
||
}
|
||
client = sequence;
|
||
}
|
||
public void acceptServer(long sequence) {
|
||
if (sequence <= server) {
|
||
throw new IllegalArgumentException("server sequence");
|
||
}
|
||
server = sequence;
|
||
}
|
||
public long clientSequence() { return client; }
|
||
public long serverSequence() { return server; }
|
||
}
|
||
|
||
public record GrpcBidiResumeState(
|
||
long lastClientApplied,
|
||
long lastServerApplied,
|
||
long generation) {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-bidi-streaming:test --tests 'io.backend.skeleton.grpc.advanced.streaming.GrpcBidiSequenceTrackerTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-bidi-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiSession.java' 'modules/grpc-advanced/grpc-bidi-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiDirectionState.java' 'modules/grpc-advanced/grpc-bidi-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiSequenceTracker.java' 'modules/grpc-advanced/grpc-bidi-streaming/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiResumeState.java' 'modules/grpc-advanced/grpc-bidi-streaming/src/test/java/io/backend/skeleton/grpc/advanced/streaming/GrpcBidiSequenceTrackerTest.java'
|
||
git commit -m "feat: define grpc bidi stream lifecycle"
|
||
```
|
||
|
||
### Task 7: Manual Flow Control 승인 API
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-manual-flow-control/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcManualFlowControlPolicy.java`
|
||
- Create: `modules/grpc-advanced/grpc-manual-flow-control/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcDemandController.java`
|
||
- Create: `modules/grpc-advanced/grpc-manual-flow-control/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcDemandDecision.java`
|
||
- Test: `modules/grpc-advanced/grpc-manual-flow-control/src/test/java/io/backend/skeleton/grpc/advanced/streaming/GrpcDemandControllerTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable bounded flow-control policy와 gRPC readiness/request APIs.
|
||
- Produces: 승인된 streaming method가 수신 demand와 outbound readiness를 직접 제어하는 GR2 API.
|
||
|
||
**Implementation requirements:**
|
||
- manual inbound request 수와 outstanding demand를 제한한다.
|
||
- application code에 raw observer를 직접 반환하지 않는다.
|
||
- read와 write 양쪽이 서로 기다리는 deadlock을 탐지하는 watchdog을 둔다.
|
||
- demand와 queue high-watermark를 metric으로 기록한다.
|
||
- manual mode가 없는 method에는 자동 flow control을 유지한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcDemandControllerTest {
|
||
@org.junit.jupiter.api.Test
|
||
void demandCannotExceedConfiguredWindow() {
|
||
var controller = new GrpcDemandController(4);
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> controller.request(5))
|
||
.isInstanceOf(IllegalArgumentException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-manual-flow-control:test --tests 'io.backend.skeleton.grpc.advanced.streaming.GrpcDemandControllerTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public final class GrpcDemandController {
|
||
private final int maxOutstanding;
|
||
private int outstanding;
|
||
|
||
public GrpcDemandController(int maxOutstanding) {
|
||
this.maxOutstanding = maxOutstanding;
|
||
}
|
||
|
||
public void request(int count) {
|
||
if (count <= 0
|
||
|| outstanding + count > maxOutstanding) {
|
||
throw new IllegalArgumentException(
|
||
"manual flow-control window exceeded");
|
||
}
|
||
outstanding += count;
|
||
}
|
||
}
|
||
|
||
public record GrpcManualFlowControlPolicy(
|
||
int maxOutstanding,
|
||
java.time.Duration deadlockWatchdog) {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-manual-flow-control:test --tests 'io.backend.skeleton.grpc.advanced.streaming.GrpcDemandControllerTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-manual-flow-control/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcManualFlowControlPolicy.java' 'modules/grpc-advanced/grpc-manual-flow-control/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcDemandController.java' 'modules/grpc-advanced/grpc-manual-flow-control/src/main/java/io/backend/skeleton/grpc/advanced/streaming/GrpcDemandDecision.java' 'modules/grpc-advanced/grpc-manual-flow-control/src/test/java/io/backend/skeleton/grpc/advanced/streaming/GrpcDemandControllerTest.java'
|
||
git commit -m "feat: add approved grpc manual flow control"
|
||
```
|
||
|
||
### Task 8: Read-only Unary Hedging
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-hedging/src/main/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingPolicy.java`
|
||
- Create: `modules/grpc-advanced/grpc-hedging/src/main/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingEligibility.java`
|
||
- Create: `modules/grpc-advanced/grpc-hedging/src/main/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingBudget.java`
|
||
- Create: `modules/grpc-advanced/grpc-hedging/src/main/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingResult.java`
|
||
- Test: `modules/grpc-advanced/grpc-hedging/src/test/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingEligibilityTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable method policy, deadline, retry owner와 Service Config.
|
||
- Produces: READ_ONLY Unary에만 제한된 duplicate attempt와 first-success 정책.
|
||
|
||
**Implementation requirements:**
|
||
- READ_ONLY + UNARY만 hedging 대상이다.
|
||
- mutation, streaming, idempotency-key write에는 hedging을 금지한다.
|
||
- maximum attempts는 초기 2로 제한한다.
|
||
- hedging delay와 total deadline·attempt budget을 함께 검증한다.
|
||
- duplicate backend load와 cancelled loser result를 metric으로 기록한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcHedgingEligibilityTest {
|
||
@org.junit.jupiter.api.Test
|
||
void mutationCannotHedge() {
|
||
var eligibility = new GrpcHedgingEligibility();
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
eligibility.allowed(
|
||
RpcIdempotencyProfile.NON_IDEMPOTENT,
|
||
RpcType.UNARY)).isFalse();
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-hedging:test --tests 'io.backend.skeleton.grpc.advanced.resilience.GrpcHedgingEligibilityTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcHedgingPolicy(
|
||
int maxAttempts,
|
||
java.time.Duration hedgingDelay) {
|
||
public GrpcHedgingPolicy {
|
||
if (maxAttempts < 2 || maxAttempts > 2) {
|
||
throw new IllegalArgumentException(
|
||
"initial hedging supports exactly two attempts");
|
||
}
|
||
}
|
||
}
|
||
|
||
public final class GrpcHedgingEligibility {
|
||
public boolean allowed(
|
||
RpcIdempotencyProfile profile,
|
||
RpcType type) {
|
||
return profile == RpcIdempotencyProfile.READ_ONLY
|
||
&& type == RpcType.UNARY;
|
||
}
|
||
}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-hedging:test --tests 'io.backend.skeleton.grpc.advanced.resilience.GrpcHedgingEligibilityTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-hedging/src/main/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingPolicy.java' 'modules/grpc-advanced/grpc-hedging/src/main/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingEligibility.java' 'modules/grpc-advanced/grpc-hedging/src/main/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingBudget.java' 'modules/grpc-advanced/grpc-hedging/src/main/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingResult.java' 'modules/grpc-advanced/grpc-hedging/src/test/java/io/backend/skeleton/grpc/advanced/resilience/GrpcHedgingEligibilityTest.java'
|
||
git commit -m "feat: add read only grpc hedging"
|
||
```
|
||
|
||
### Task 9: Custom Name Resolver SPI
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-custom-resolver/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcEndpointSnapshot.java`
|
||
- Create: `modules/grpc-advanced/grpc-custom-resolver/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcResolverUpdate.java`
|
||
- Create: `modules/grpc-advanced/grpc-custom-resolver/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcCustomResolver.java`
|
||
- Create: `modules/grpc-advanced/grpc-custom-resolver/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcResolverSafetyPolicy.java`
|
||
- Test: `modules/grpc-advanced/grpc-custom-resolver/src/test/java/io/backend/skeleton/grpc/advanced/discovery/GrpcResolverSafetyPolicyTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable Named Channel Profile and custom discovery event source.
|
||
- Produces: backend address와 Service Config update를 versioned snapshot으로 전달하는 승인된 resolver SPI.
|
||
|
||
**Implementation requirements:**
|
||
- resolver update는 monotonic revision과 endpoint set을 가진다.
|
||
- 빈 endpoint update, stale revision, invalid authority를 거부한다.
|
||
- resolver는 credential·business metadata를 제공하지 않는다.
|
||
- Service Config를 함께 제공할 경우 retry owner·LB policy 검증을 통과해야 한다.
|
||
- resolver close 후 update를 수신하지 않는다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcResolverSafetyPolicyTest {
|
||
@org.junit.jupiter.api.Test
|
||
void staleRevisionIsRejected() {
|
||
var policy = new GrpcResolverSafetyPolicy();
|
||
policy.accept(new GrpcEndpointSnapshot(2,
|
||
java.util.Set.of("10.0.0.2:9090")));
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> policy.accept(new GrpcEndpointSnapshot(
|
||
1, java.util.Set.of("10.0.0.1:9090"))))
|
||
.isInstanceOf(IllegalArgumentException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-custom-resolver:test --tests 'io.backend.skeleton.grpc.advanced.discovery.GrpcResolverSafetyPolicyTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcEndpointSnapshot(
|
||
long revision,
|
||
java.util.Set<String> endpoints) {}
|
||
|
||
public final class GrpcResolverSafetyPolicy {
|
||
private long revision = -1;
|
||
public void accept(GrpcEndpointSnapshot snapshot) {
|
||
if (snapshot.endpoints().isEmpty()
|
||
|| snapshot.revision() <= revision) {
|
||
throw new IllegalArgumentException(
|
||
"invalid resolver update");
|
||
}
|
||
revision = snapshot.revision();
|
||
}
|
||
}
|
||
|
||
public interface GrpcCustomResolver
|
||
extends AutoCloseable {
|
||
void start(
|
||
java.util.function.Consumer<GrpcResolverUpdate> listener);
|
||
}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-custom-resolver:test --tests 'io.backend.skeleton.grpc.advanced.discovery.GrpcResolverSafetyPolicyTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-custom-resolver/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcEndpointSnapshot.java' 'modules/grpc-advanced/grpc-custom-resolver/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcResolverUpdate.java' 'modules/grpc-advanced/grpc-custom-resolver/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcCustomResolver.java' 'modules/grpc-advanced/grpc-custom-resolver/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcResolverSafetyPolicy.java' 'modules/grpc-advanced/grpc-custom-resolver/src/test/java/io/backend/skeleton/grpc/advanced/discovery/GrpcResolverSafetyPolicyTest.java'
|
||
git commit -m "feat: add grpc custom resolver spi"
|
||
```
|
||
|
||
### Task 10: Custom Load Balancer SPI
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-custom-load-balancer/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcEndpointCandidate.java`
|
||
- Create: `modules/grpc-advanced/grpc-custom-load-balancer/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcLoadBalancerPicker.java`
|
||
- Create: `modules/grpc-advanced/grpc-custom-load-balancer/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcLoadBalancerDecision.java`
|
||
- Create: `modules/grpc-advanced/grpc-custom-load-balancer/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcLoadBalancerSafetyPolicy.java`
|
||
- Test: `modules/grpc-advanced/grpc-custom-load-balancer/src/test/java/io/backend/skeleton/grpc/advanced/discovery/GrpcLoadBalancerSafetyPolicyTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Task 9 custom resolver snapshot and Stable channel profile.
|
||
- Produces: bounded endpoint candidate에서 picker가 결정을 내리고 unsafe policy를 차단하는 GR3 SPI.
|
||
|
||
**Implementation requirements:**
|
||
- picker는 resolver가 제공한 endpoint만 선택할 수 있다.
|
||
- endpoint health, connectivity, weight와 ejection state를 bounded metadata로 사용한다.
|
||
- business request body·tenant ID를 routing input으로 사용하지 않는다.
|
||
- custom picker failure는 deterministic fallback 또는 call failure로 드러난다.
|
||
- load-aware/weighted policy는 performance·fairness·failover evidence를 요구한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcLoadBalancerSafetyPolicyTest {
|
||
@org.junit.jupiter.api.Test
|
||
void pickerCannotChooseUnknownEndpoint() {
|
||
var policy = new GrpcLoadBalancerSafetyPolicy(
|
||
java.util.Set.of("a:9090"));
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> policy.validate("b:9090"))
|
||
.isInstanceOf(IllegalArgumentException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-custom-load-balancer:test --tests 'io.backend.skeleton.grpc.advanced.discovery.GrpcLoadBalancerSafetyPolicyTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcEndpointCandidate(
|
||
String authority,
|
||
int weight,
|
||
boolean ready) {}
|
||
|
||
public final class GrpcLoadBalancerSafetyPolicy {
|
||
private final java.util.Set<String> endpoints;
|
||
public GrpcLoadBalancerSafetyPolicy(
|
||
java.util.Set<String> endpoints) {
|
||
this.endpoints = java.util.Set.copyOf(endpoints);
|
||
}
|
||
public void validate(String selected) {
|
||
if (!endpoints.contains(selected)) {
|
||
throw new IllegalArgumentException(
|
||
"picker selected unknown endpoint");
|
||
}
|
||
}
|
||
}
|
||
|
||
public interface GrpcLoadBalancerPicker {
|
||
GrpcLoadBalancerDecision pick(
|
||
java.util.List<GrpcEndpointCandidate> candidates);
|
||
}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-custom-load-balancer:test --tests 'io.backend.skeleton.grpc.advanced.discovery.GrpcLoadBalancerSafetyPolicyTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-custom-load-balancer/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcEndpointCandidate.java' 'modules/grpc-advanced/grpc-custom-load-balancer/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcLoadBalancerPicker.java' 'modules/grpc-advanced/grpc-custom-load-balancer/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcLoadBalancerDecision.java' 'modules/grpc-advanced/grpc-custom-load-balancer/src/main/java/io/backend/skeleton/grpc/advanced/discovery/GrpcLoadBalancerSafetyPolicy.java' 'modules/grpc-advanced/grpc-custom-load-balancer/src/test/java/io/backend/skeleton/grpc/advanced/discovery/GrpcLoadBalancerSafetyPolicyTest.java'
|
||
git commit -m "feat: add grpc custom load balancer spi"
|
||
```
|
||
|
||
### Task 11: xDS Proxyless Experimental Profile
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-xds/src/main/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsProfile.java`
|
||
- Create: `modules/grpc-advanced/grpc-xds/src/main/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsResourceSnapshot.java`
|
||
- Create: `modules/grpc-advanced/grpc-xds/src/main/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsStartupGuard.java`
|
||
- Create: `modules/grpc-advanced/grpc-xds/src/main/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsFailurePolicy.java`
|
||
- Create: `modules/grpc-advanced/grpc-xds/src/test/resources/xds/bootstrap.json`
|
||
- Test: `modules/grpc-advanced/grpc-xds/src/test/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsStartupGuardTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable channel/security policy, xDS bootstrap and control-plane resources.
|
||
- Produces: xds target, resource discovery, traffic policy, mTLS와 fallback을 별도 Experimental profile로 검증.
|
||
|
||
**Implementation requirements:**
|
||
- `xds:///` target만 explicit xDS profile에서 허용한다.
|
||
- bootstrap·control-plane credential·resource namespace를 검증한다.
|
||
- application YAML과 xDS에 retry/LB owner를 중복 정의하지 않는다.
|
||
- resource not found, stale resource, control-plane outage와 last-known-good policy를 명시한다.
|
||
- xDS 기능을 Stable DNS/LB support로 광고하지 않는다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcXdsStartupGuardTest {
|
||
@org.junit.jupiter.api.Test
|
||
void xdsRequiresExplicitFeatureFlagAndBootstrap() {
|
||
var guard = new GrpcXdsStartupGuard(false);
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> guard.validate(
|
||
new GrpcXdsProfile(
|
||
"xds:///document-service", "")))
|
||
.isInstanceOf(IllegalStateException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-xds:test --tests 'io.backend.skeleton.grpc.advanced.xds.GrpcXdsStartupGuardTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcXdsProfile(
|
||
String target,
|
||
String bootstrapResource) {}
|
||
|
||
public final class GrpcXdsStartupGuard {
|
||
private final boolean enabled;
|
||
public GrpcXdsStartupGuard(boolean enabled) {
|
||
this.enabled = enabled;
|
||
}
|
||
public void validate(GrpcXdsProfile profile) {
|
||
if (!enabled
|
||
|| !profile.target().startsWith("xds:///")
|
||
|| profile.bootstrapResource().isBlank()) {
|
||
throw new IllegalStateException(
|
||
"valid xDS experimental profile is required");
|
||
}
|
||
}
|
||
}
|
||
|
||
public record GrpcXdsResourceSnapshot(
|
||
String version,
|
||
java.util.Set<String> clusters,
|
||
java.time.Instant receivedAt) {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-xds:test --tests 'io.backend.skeleton.grpc.advanced.xds.GrpcXdsStartupGuardTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-xds/src/main/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsProfile.java' 'modules/grpc-advanced/grpc-xds/src/main/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsResourceSnapshot.java' 'modules/grpc-advanced/grpc-xds/src/main/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsStartupGuard.java' 'modules/grpc-advanced/grpc-xds/src/main/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsFailurePolicy.java' 'modules/grpc-advanced/grpc-xds/src/test/resources/xds/bootstrap.json' 'modules/grpc-advanced/grpc-xds/src/test/java/io/backend/skeleton/grpc/advanced/xds/GrpcXdsStartupGuardTest.java'
|
||
git commit -m "feat: add experimental grpc xds profile"
|
||
```
|
||
|
||
### Task 12: gRPC-Web Unary·Server Streaming Bridge
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-web/src/main/java/io/backend/skeleton/grpc/advanced/web/GrpcWebRpcSupport.java`
|
||
- Create: `modules/grpc-advanced/grpc-web/src/main/java/io/backend/skeleton/grpc/advanced/web/GrpcWebProfile.java`
|
||
- Create: `modules/grpc-advanced/grpc-web/src/main/java/io/backend/skeleton/grpc/advanced/web/GrpcWebProxyContract.java`
|
||
- Create: `modules/grpc-advanced/grpc-web/src/main/java/io/backend/skeleton/grpc/advanced/web/GrpcWebCompatibilityGate.java`
|
||
- Create: `modules/grpc-advanced/grpc-web/src/test/resources/envoy/envoy.yaml`
|
||
- Test: `modules/grpc-advanced/grpc-web/src/test/java/io/backend/skeleton/grpc/advanced/web/GrpcWebCompatibilityGateTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable proto contract, Envoy/proxy profile and browser gRPC-Web client contract.
|
||
- Produces: 브라우저에서 Unary와 grpcwebtext Server Streaming만 지원하고 Client/Bidi를 명시적으로 차단하는 bridge.
|
||
|
||
**Implementation requirements:**
|
||
- 지원 RPC는 Unary와 Server Streaming이다.
|
||
- Client Streaming과 Bidirectional Streaming을 지원한다고 선언하지 않는다.
|
||
- Envoy 또는 승인된 proxy의 CORS, TLS, metadata/header mapping을 검증한다.
|
||
- browser credential·cookie·bearer profile과 CSRF/CORS 정책을 분리한다.
|
||
- native gRPC와 gRPC-Web compatibility suite를 같은 schema에 실행한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcWebCompatibilityGateTest {
|
||
@org.junit.jupiter.api.Test
|
||
void bidiStreamingIsUnsupported() {
|
||
var gate = GrpcWebCompatibilityGate.standard();
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
gate.supports(RpcType.BIDI_STREAMING)).isFalse();
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
gate.supports(RpcType.UNARY)).isTrue();
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-web:test --tests 'io.backend.skeleton.grpc.advanced.web.GrpcWebCompatibilityGateTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public enum GrpcWebRpcSupport {
|
||
UNARY, SERVER_STREAMING
|
||
}
|
||
|
||
public final class GrpcWebCompatibilityGate {
|
||
public static GrpcWebCompatibilityGate standard() {
|
||
return new GrpcWebCompatibilityGate();
|
||
}
|
||
|
||
public boolean supports(RpcType type) {
|
||
return type == RpcType.UNARY
|
||
|| type == RpcType.SERVER_STREAMING;
|
||
}
|
||
}
|
||
|
||
public record GrpcWebProfile(
|
||
String proxy,
|
||
boolean tlsRequired,
|
||
java.util.Set<String> allowedOrigins) {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-web:test --tests 'io.backend.skeleton.grpc.advanced.web.GrpcWebCompatibilityGateTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-web/src/main/java/io/backend/skeleton/grpc/advanced/web/GrpcWebRpcSupport.java' 'modules/grpc-advanced/grpc-web/src/main/java/io/backend/skeleton/grpc/advanced/web/GrpcWebProfile.java' 'modules/grpc-advanced/grpc-web/src/main/java/io/backend/skeleton/grpc/advanced/web/GrpcWebProxyContract.java' 'modules/grpc-advanced/grpc-web/src/main/java/io/backend/skeleton/grpc/advanced/web/GrpcWebCompatibilityGate.java' 'modules/grpc-advanced/grpc-web/src/test/resources/envoy/envoy.yaml' 'modules/grpc-advanced/grpc-web/src/test/java/io/backend/skeleton/grpc/advanced/web/GrpcWebCompatibilityGateTest.java'
|
||
git commit -m "feat: add grpc web compatibility bridge"
|
||
```
|
||
|
||
### Task 13: Servlet HTTP/2 Compatibility Profile
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-servlet-compat/src/main/java/io/backend/skeleton/grpc/advanced/servlet/GrpcServletCompatibilityProfile.java`
|
||
- Create: `modules/grpc-advanced/grpc-servlet-compat/src/main/java/io/backend/skeleton/grpc/advanced/servlet/GrpcServletCapabilityMatrix.java`
|
||
- Create: `modules/grpc-advanced/grpc-servlet-compat/src/main/java/io/backend/skeleton/grpc/advanced/servlet/GrpcServletStartupValidator.java`
|
||
- Test: `modules/grpc-advanced/grpc-servlet-compat/src/test/java/io/backend/skeleton/grpc/advanced/servlet/GrpcServletStartupValidatorTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable server profile and Spring Servlet container HTTP/2 integration.
|
||
- Produces: 동일 web server/port를 사용하는 Servlet transport의 지원·비지원 기능을 명시하는 compatibility module.
|
||
|
||
**Implementation requirements:**
|
||
- Servlet transport는 Stable Netty certification을 대체하지 않는다.
|
||
- container가 network layer를 소유하므로 native Netty-only 설정을 요청하면 실패한다.
|
||
- HTTP/2, TLS, message/metadata limits, health, reflection, drain을 실제 container에서 검증한다.
|
||
- keepalive·connection age·flow-control capability 차이를 matrix에 기록한다.
|
||
- Servlet profile은 명시적 feature flag와 compatibility release gate를 요구한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcServletStartupValidatorTest {
|
||
@org.junit.jupiter.api.Test
|
||
void nativeOnlyFlowControlSettingIsRejected() {
|
||
var validator = new GrpcServletStartupValidator();
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> validator.validate(
|
||
new GrpcServletCompatibilityProfile(true)))
|
||
.isInstanceOf(IllegalArgumentException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-servlet-compat:test --tests 'io.backend.skeleton.grpc.advanced.servlet.GrpcServletStartupValidatorTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcServletCompatibilityProfile(
|
||
boolean requestsNativeNettyFlowControl) {}
|
||
|
||
public final class GrpcServletStartupValidator {
|
||
public void validate(
|
||
GrpcServletCompatibilityProfile profile) {
|
||
if (profile.requestsNativeNettyFlowControl()) {
|
||
throw new IllegalArgumentException(
|
||
"Servlet transport cannot promise Netty-only policy");
|
||
}
|
||
}
|
||
}
|
||
|
||
public record GrpcServletCapabilityMatrix(
|
||
boolean http2,
|
||
boolean tls,
|
||
boolean health,
|
||
boolean reflection,
|
||
boolean nettyFlowControl) {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-servlet-compat:test --tests 'io.backend.skeleton.grpc.advanced.servlet.GrpcServletStartupValidatorTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-servlet-compat/src/main/java/io/backend/skeleton/grpc/advanced/servlet/GrpcServletCompatibilityProfile.java' 'modules/grpc-advanced/grpc-servlet-compat/src/main/java/io/backend/skeleton/grpc/advanced/servlet/GrpcServletCapabilityMatrix.java' 'modules/grpc-advanced/grpc-servlet-compat/src/main/java/io/backend/skeleton/grpc/advanced/servlet/GrpcServletStartupValidator.java' 'modules/grpc-advanced/grpc-servlet-compat/src/test/java/io/backend/skeleton/grpc/advanced/servlet/GrpcServletStartupValidatorTest.java'
|
||
git commit -m "feat: add grpc servlet compatibility profile"
|
||
```
|
||
|
||
### Task 14: Spring Integration gRPC Bridge
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-integration-bridge/src/main/java/io/backend/skeleton/grpc/advanced/integration/GrpcIntegrationInboundGateway.java`
|
||
- Create: `modules/grpc-advanced/grpc-integration-bridge/src/main/java/io/backend/skeleton/grpc/advanced/integration/GrpcIntegrationOutboundGateway.java`
|
||
- Create: `modules/grpc-advanced/grpc-integration-bridge/src/main/java/io/backend/skeleton/grpc/advanced/integration/GrpcIntegrationBridgePolicy.java`
|
||
- Test: `modules/grpc-advanced/grpc-integration-bridge/src/test/java/io/backend/skeleton/grpc/advanced/integration/GrpcIntegrationBridgePolicyTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable Generated service/stub, Spring Integration Message flow and method policy.
|
||
- Produces: Spring `Message` 기반 Integration Flow를 typed RPC 계약과 분리해 연결하는 선택 bridge.
|
||
|
||
**Implementation requirements:**
|
||
- bridge가 일반 Generated Stub/Service API를 대체한다고 설명하지 않는다.
|
||
- message header를 gRPC metadata로 무제한 복사하지 않는다.
|
||
- deadline, security, status, error, observability Stable policy를 그대로 적용한다.
|
||
- payload type은 registered converter를 통해 Proto message와 변환한다.
|
||
- broker-style ACK·durability semantics를 bridge에 추가하지 않는다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcIntegrationBridgePolicyTest {
|
||
@org.junit.jupiter.api.Test
|
||
void arbitraryHeadersAreNotForwarded() {
|
||
var policy = GrpcIntegrationBridgePolicy.standard();
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
policy.forwardedHeaders())
|
||
.containsExactlyInAnyOrder(
|
||
"correlation-id", "traceparent");
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-integration-bridge:test --tests 'io.backend.skeleton.grpc.advanced.integration.GrpcIntegrationBridgePolicyTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcIntegrationBridgePolicy(
|
||
java.util.Set<String> forwardedHeaders) {
|
||
|
||
public static GrpcIntegrationBridgePolicy standard() {
|
||
return new GrpcIntegrationBridgePolicy(
|
||
java.util.Set.of(
|
||
"correlation-id", "traceparent"));
|
||
}
|
||
}
|
||
|
||
public interface GrpcIntegrationInboundGateway {}
|
||
public interface GrpcIntegrationOutboundGateway {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-integration-bridge:test --tests 'io.backend.skeleton.grpc.advanced.integration.GrpcIntegrationBridgePolicyTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-integration-bridge/src/main/java/io/backend/skeleton/grpc/advanced/integration/GrpcIntegrationInboundGateway.java' 'modules/grpc-advanced/grpc-integration-bridge/src/main/java/io/backend/skeleton/grpc/advanced/integration/GrpcIntegrationOutboundGateway.java' 'modules/grpc-advanced/grpc-integration-bridge/src/main/java/io/backend/skeleton/grpc/advanced/integration/GrpcIntegrationBridgePolicy.java' 'modules/grpc-advanced/grpc-integration-bridge/src/test/java/io/backend/skeleton/grpc/advanced/integration/GrpcIntegrationBridgePolicyTest.java'
|
||
git commit -m "feat: add spring integration grpc bridge"
|
||
```
|
||
|
||
### Task 15: Reactor gRPC Adapter
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-reactor/src/main/java/io/backend/skeleton/grpc/advanced/reactor/ReactiveGrpcClient.java`
|
||
- Create: `modules/grpc-advanced/grpc-reactor/src/main/java/io/backend/skeleton/grpc/advanced/reactor/ReactiveGrpcServerAdapter.java`
|
||
- Create: `modules/grpc-advanced/grpc-reactor/src/main/java/io/backend/skeleton/grpc/advanced/reactor/GrpcReactorContextBridge.java`
|
||
- Create: `modules/grpc-advanced/grpc-reactor/src/main/java/io/backend/skeleton/grpc/advanced/reactor/GrpcReactorCancellationBridge.java`
|
||
- Test: `modules/grpc-advanced/grpc-reactor/src/test/java/io/backend/skeleton/grpc/advanced/reactor/GrpcReactorContextBridgeTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable typed stub/service adapter, Reactor Context and cancellation contracts.
|
||
- Produces: Unary·Server Streaming을 Mono/Flux로 노출하면서 context·deadline·cancel·backpressure를 유지하는 adapter.
|
||
|
||
**Implementation requirements:**
|
||
- Core public contract를 Reactor에 종속시키지 않는다.
|
||
- Mono/Flux cancellation을 gRPC call cancellation으로 전파한다.
|
||
- gRPC Context와 Reactor Context의 Actor·Tenant·Trace·Deadline을 bridge한다.
|
||
- blocking JPA/SDK 호출을 event loop에서 실행하지 않는다.
|
||
- server stream Flux가 Stable bounded flow-control contract를 우회하지 않는다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcReactorContextBridgeTest {
|
||
@org.junit.jupiter.api.Test
|
||
void missingActorContextFailsClosed() {
|
||
var bridge = new GrpcReactorContextBridge();
|
||
|
||
org.assertj.core.api.Assertions.assertThatThrownBy(
|
||
() -> bridge.requireContext(java.util.Map.of()))
|
||
.isInstanceOf(IllegalStateException.class);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-reactor:test --tests 'io.backend.skeleton.grpc.advanced.reactor.GrpcReactorContextBridgeTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public final class GrpcReactorContextBridge {
|
||
public GrpcRequestContext requireContext(
|
||
java.util.Map<String, Object> context) {
|
||
var value = context.get("grpcRequestContext");
|
||
if (!(value instanceof GrpcRequestContext requestContext)) {
|
||
throw new IllegalStateException(
|
||
"gRPC request context is required");
|
||
}
|
||
return requestContext;
|
||
}
|
||
}
|
||
|
||
public interface ReactiveGrpcClient {}
|
||
public interface ReactiveGrpcServerAdapter {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-reactor:test --tests 'io.backend.skeleton.grpc.advanced.reactor.GrpcReactorContextBridgeTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-reactor/src/main/java/io/backend/skeleton/grpc/advanced/reactor/ReactiveGrpcClient.java' 'modules/grpc-advanced/grpc-reactor/src/main/java/io/backend/skeleton/grpc/advanced/reactor/ReactiveGrpcServerAdapter.java' 'modules/grpc-advanced/grpc-reactor/src/main/java/io/backend/skeleton/grpc/advanced/reactor/GrpcReactorContextBridge.java' 'modules/grpc-advanced/grpc-reactor/src/main/java/io/backend/skeleton/grpc/advanced/reactor/GrpcReactorCancellationBridge.java' 'modules/grpc-advanced/grpc-reactor/src/test/java/io/backend/skeleton/grpc/advanced/reactor/GrpcReactorContextBridgeTest.java'
|
||
git commit -m "feat: add reactor grpc adapter"
|
||
```
|
||
|
||
### Task 16: Kotlin Coroutine·Flow Adapter
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-kotlin/src/main/java/io/backend/skeleton/grpc/advanced/kotlin/GrpcKotlinProfile.java`
|
||
- Create: `modules/grpc-advanced/grpc-kotlin/src/main/java/io/backend/skeleton/grpc/advanced/kotlin/GrpcCoroutineContextBridge.java`
|
||
- Create: `modules/grpc-advanced/grpc-kotlin/src/main/java/io/backend/skeleton/grpc/advanced/kotlin/GrpcKotlinCompatibilityGate.java`
|
||
- Create: `modules/grpc-advanced/grpc-kotlin/src/main/kotlin/io/backend/skeleton/grpc/advanced/kotlin/GrpcCoroutineAdapter.kt`
|
||
- Test: `modules/grpc-advanced/grpc-kotlin/src/test/java/io/backend/skeleton/grpc/advanced/kotlin/GrpcKotlinCompatibilityGateTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Stable schema artifact, Kotlin generated code and coroutine/Flow runtime.
|
||
- Produces: Kotlin coroutine unary와 Flow streaming을 Stable method policy·evidence와 연결하는 optional adapter.
|
||
|
||
**Implementation requirements:**
|
||
- Java generated contract와 Kotlin generated contract의 schema source를 하나로 유지한다.
|
||
- Coroutine cancellation을 gRPC cancellation으로 전파한다.
|
||
- Flow backpressure가 Stable stream buffer limit을 우회하지 않는다.
|
||
- Kotlin adapter가 Java Core의 evidence·status·deadline types를 보존한다.
|
||
- Kotlin toolchain compatibility를 별도 release lane에서 검증한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcKotlinCompatibilityGateTest {
|
||
@org.junit.jupiter.api.Test
|
||
void schemaDigestMustMatchJavaLane() {
|
||
var gate = new GrpcKotlinCompatibilityGate();
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
gate.compatible("sha256:a", "sha256:b"))
|
||
.isFalse();
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-kotlin:test --tests 'io.backend.skeleton.grpc.advanced.kotlin.GrpcKotlinCompatibilityGateTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public final class GrpcKotlinCompatibilityGate {
|
||
public boolean compatible(
|
||
String javaSchemaDigest,
|
||
String kotlinSchemaDigest) {
|
||
return javaSchemaDigest.equals(kotlinSchemaDigest);
|
||
}
|
||
}
|
||
|
||
public record GrpcKotlinProfile(
|
||
String kotlinVersion,
|
||
String grpcKotlinVersion,
|
||
boolean coroutineCancellationEnabled) {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-kotlin:test --tests 'io.backend.skeleton.grpc.advanced.kotlin.GrpcKotlinCompatibilityGateTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-kotlin/src/main/java/io/backend/skeleton/grpc/advanced/kotlin/GrpcKotlinProfile.java' 'modules/grpc-advanced/grpc-kotlin/src/main/java/io/backend/skeleton/grpc/advanced/kotlin/GrpcCoroutineContextBridge.java' 'modules/grpc-advanced/grpc-kotlin/src/main/java/io/backend/skeleton/grpc/advanced/kotlin/GrpcKotlinCompatibilityGate.java' 'modules/grpc-advanced/grpc-kotlin/src/main/kotlin/io/backend/skeleton/grpc/advanced/kotlin/GrpcCoroutineAdapter.kt' 'modules/grpc-advanced/grpc-kotlin/src/test/java/io/backend/skeleton/grpc/advanced/kotlin/GrpcKotlinCompatibilityGateTest.java'
|
||
git commit -m "feat: add kotlin grpc adapter"
|
||
```
|
||
|
||
### Task 17: Channelz·CSDS 진단과 Advanced Testkit
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-channel-diagnostics/src/main/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcChannelDiagnosticsSnapshot.java`
|
||
- Create: `modules/grpc-advanced/grpc-channel-diagnostics/src/main/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcChannelDiagnosticsPolicy.java`
|
||
- Create: `modules/grpc-advanced/grpc-channel-diagnostics/src/main/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcAdvancedInfrastructureTestkit.java`
|
||
- Create: `modules/grpc-advanced/grpc-channel-diagnostics/src/main/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcDiagnosticsRedactor.java`
|
||
- Create: `modules/grpc-advanced/grpc-channel-diagnostics/src/test/resources/xds/control-plane-snapshot.json`
|
||
- Create: `modules/grpc-advanced/grpc-channel-diagnostics/src/test/resources/grpc-web/envoy.yaml`
|
||
- Test: `modules/grpc-advanced/grpc-channel-diagnostics/src/test/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcChannelDiagnosticsPolicyTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Channel runtime, xDS profile, server state and admin authorization.
|
||
- Produces: Channelz/CSDS 기반 channel·subchannel·socket·xDS 상태를 관리자에게 제한적으로 제공하는 진단 계층.
|
||
|
||
**Implementation requirements:**
|
||
- diagnostics endpoint는 admin network·role을 요구한다.
|
||
- socket address·authority는 정책에 따라 마스킹한다.
|
||
- token, certificate private material, metadata와 payload를 노출하지 않는다.
|
||
- xDS CSDS는 xDS feature가 활성화된 경우에만 등록한다.
|
||
- testkit은 gRPC-Web proxy, Servlet container, xDS control-plane failure를 검증한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcChannelDiagnosticsPolicyTest {
|
||
@org.junit.jupiter.api.Test
|
||
void anonymousAccessIsDenied() {
|
||
var policy = GrpcChannelDiagnosticsPolicy.adminOnly();
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
policy.allowed(java.util.Set.of())).isFalse();
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-channel-diagnostics:test --tests 'io.backend.skeleton.grpc.advanced.diagnostics.GrpcChannelDiagnosticsPolicyTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public record GrpcChannelDiagnosticsPolicy(
|
||
java.util.Set<String> requiredRoles) {
|
||
public static GrpcChannelDiagnosticsPolicy adminOnly() {
|
||
return new GrpcChannelDiagnosticsPolicy(
|
||
java.util.Set.of("GRPC_ADMIN"));
|
||
}
|
||
public boolean allowed(java.util.Set<String> roles) {
|
||
return roles.containsAll(requiredRoles);
|
||
}
|
||
}
|
||
|
||
public record GrpcChannelDiagnosticsSnapshot(
|
||
String channelProfile,
|
||
String connectivityState,
|
||
int subchannels,
|
||
int sockets,
|
||
java.time.Instant capturedAt) {}
|
||
|
||
public interface GrpcAdvancedInfrastructureTestkit {}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-channel-diagnostics:test --tests 'io.backend.skeleton.grpc.advanced.diagnostics.GrpcChannelDiagnosticsPolicyTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-channel-diagnostics/src/main/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcChannelDiagnosticsSnapshot.java' 'modules/grpc-advanced/grpc-channel-diagnostics/src/main/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcChannelDiagnosticsPolicy.java' 'modules/grpc-advanced/grpc-channel-diagnostics/src/main/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcAdvancedInfrastructureTestkit.java' 'modules/grpc-advanced/grpc-channel-diagnostics/src/main/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcDiagnosticsRedactor.java' 'modules/grpc-advanced/grpc-channel-diagnostics/src/test/resources/xds/control-plane-snapshot.json' 'modules/grpc-advanced/grpc-channel-diagnostics/src/test/resources/grpc-web/envoy.yaml' 'modules/grpc-advanced/grpc-channel-diagnostics/src/test/java/io/backend/skeleton/grpc/advanced/diagnostics/GrpcChannelDiagnosticsPolicyTest.java'
|
||
git commit -m "feat: add grpc channel diagnostics and testkit"
|
||
```
|
||
|
||
### Task 18: Advanced Capability Promotion Gate
|
||
|
||
**Files:**
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedPromotionEvidence.java`
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedPromotionDecision.java`
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedPromotionGate.java`
|
||
- Create: `modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedSupportMatrix.java`
|
||
- Create: `docs/compatibility/grpc-advanced-support-matrix.md`
|
||
- Create: `docs/runbooks/grpc-advanced-capabilities.md`
|
||
- Create: `docs/adr/ADR-065-grpc-advanced-capability-promotion.md`
|
||
- Test: `modules/grpc-advanced/grpc-advanced-bootstrap/src/test/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedPromotionGateTest.java`
|
||
|
||
**Interfaces:**
|
||
- Consumes: Tasks 1–17의 capability-specific compatibility, security, fault, performance, soak evidence.
|
||
- Produces: 각 Advanced/Experimental capability를 독립적으로 승격·차단하고 Stable starter 유입을 관리하는 release gate.
|
||
|
||
**Implementation requirements:**
|
||
- capability별 required evidence를 분리한다.
|
||
- Edition, streaming, xDS, gRPC-Web, Servlet, language adapter가 서로의 승격을 묶지 않는다.
|
||
- Experimental에서 Advanced Stable로 승격하려면 ADR, runbook, actual environment test와 soak evidence가 필요하다.
|
||
- Stable default로 승격하려면 dependency·security·operational cost 재검토가 필요하다.
|
||
- 미승격 capability는 feature flag와 별도 module로 유지한다.
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```java
|
||
class GrpcAdvancedPromotionGateTest {
|
||
@org.junit.jupiter.api.Test
|
||
void xdsWithoutControlPlaneSoakIsBlocked() {
|
||
var evidence = new GrpcAdvancedPromotionEvidence(
|
||
GrpcAdvancedCapability.XDS,
|
||
java.util.Set.of(
|
||
"compatibility", "security", "fault", "adr"));
|
||
var gate = new GrpcAdvancedPromotionGate();
|
||
|
||
org.assertj.core.api.Assertions.assertThat(
|
||
gate.decide(evidence))
|
||
.isEqualTo(GrpcAdvancedPromotionDecision.BLOCKED);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test and verify the failure**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-advanced-bootstrap:test --tests 'io.backend.skeleton.grpc.advanced.release.GrpcAdvancedPromotionGateTest'
|
||
```
|
||
|
||
Expected: FAIL because the Advanced production contract does not exist or the capability bypasses the Stable guardrail.
|
||
|
||
- [ ] **Step 3: Implement the smallest complete production contract**
|
||
|
||
```java
|
||
public enum GrpcAdvancedPromotionDecision {
|
||
ADVANCED_STABLE, EXPERIMENTAL, BLOCKED
|
||
}
|
||
|
||
public record GrpcAdvancedPromotionEvidence(
|
||
GrpcAdvancedCapability capability,
|
||
java.util.Set<String> passed) {}
|
||
|
||
public final class GrpcAdvancedPromotionGate {
|
||
public GrpcAdvancedPromotionDecision decide(
|
||
GrpcAdvancedPromotionEvidence evidence) {
|
||
var required = java.util.Set.of(
|
||
"compatibility", "security", "fault",
|
||
"performance", "soak", "adr", "runbook");
|
||
return evidence.passed().containsAll(required)
|
||
? GrpcAdvancedPromotionDecision.ADVANCED_STABLE
|
||
: GrpcAdvancedPromotionDecision.BLOCKED;
|
||
}
|
||
}
|
||
```
|
||
|
||
Implement every listed production file with the exact names and invariants above. Preserve the Stable method policy, security, deadline, evidence, error and observability contracts; an Advanced capability is not a raw escape hatch.
|
||
|
||
- [ ] **Step 4: Run the focused test and Advanced aggregate suite**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
./gradlew :modules:grpc-advanced:grpc-advanced-bootstrap:test --tests 'io.backend.skeleton.grpc.advanced.release.GrpcAdvancedPromotionGateTest'
|
||
./gradlew grpcAdvancedTest
|
||
```
|
||
|
||
Expected: PASS for the focused test and Advanced aggregate suite.
|
||
|
||
- [ ] **Step 5: Commit the independently reviewable change**
|
||
|
||
```bash
|
||
git add 'modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedPromotionEvidence.java' 'modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedPromotionDecision.java' 'modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedPromotionGate.java' 'modules/grpc-advanced/grpc-advanced-bootstrap/src/main/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedSupportMatrix.java' 'docs/compatibility/grpc-advanced-support-matrix.md' 'docs/runbooks/grpc-advanced-capabilities.md' 'docs/adr/ADR-065-grpc-advanced-capability-promotion.md' 'modules/grpc-advanced/grpc-advanced-bootstrap/src/test/java/io/backend/skeleton/grpc/advanced/release/GrpcAdvancedPromotionGateTest.java'
|
||
git commit -m "docs: complete grpc advanced promotion gate"
|
||
```
|