103 KiB
GraphQL Advanced Capability Expansion Implementation 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 GraphQL API 실행 플랫폼 위에 Persisted Operation, WebSocket·SSE Subscription, optional replay, Federation Subgraph, Code Generation과 제한된 Spring Data 호환 기능을 추가하고 RSocket·HTTP GET·Incremental Delivery를 격리된 Experimental capability로 검증한다.
Architecture: Advanced capability는 modules/graphql-advanced에만 존재하며 Stable module의 public contract를 소비하되 Stable starter의 transitive dependency가 되지 않는다. 모든 capability는 backend.graphql.advanced.* feature flag와 capability별 release evidence를 요구하며, 장기 연결·schema composition·provider-specific behavior가 Stable query/mutation path를 오염시키지 않도록 분리한다.
Tech Stack: Stable GraphQL platform, Java 21, Spring Boot 4.1 BOM, Spring for GraphQL 2.0, Boot-managed GraphQL Java v25, GraphQL Java DataLoader, Spring WebFlux WebSocket, Spring GraphQL SSE/RSocket, federation-jvm, Micrometer, JUnit 5, Reactor Test.
Global Constraints
- 이 계획은 Stable 구현 계획 Task
1–48과 Stable Release Gate가 통과한 후 시작한다. - Advanced module root는
modules/graphql-advanced이다. - Root package는
io.backend.skeleton.graphql.advanced이다. - 모든 capability는
backend.graphql.advanced.*아래의 명시적 feature flag를 요구한다. - Stable starter는 Advanced module에 compile·runtime dependency를 갖지 않는다.
- Persisted Operation은 Parse cache 및 Response cache와 다른 기능이다.
- WebSocket protocol은
graphql-transport-ws만 지원하고subscriptions-transport-ws를 신규 지원하지 않는다. - GraphQL Subscription은 Messaging의 ACK·offset·replay·DLQ를 대체하지 않는다.
- Slow consumer 기본 정책은 무음 event drop이 아니라 종료다.
- SSE는 Distinct Connection subscription transport이며 Query·Mutation response mode가 아니다.
- Replay extension의 durability·offset guarantee는 Messaging platform이 소유한다.
- Single Executable Schema는 계속 Stable 기본값이다.
- Federation은 Subgraph capability만 이 저장소에 구현하고 Router/Supergraph 운영은 별도 프로젝트가 소유한다.
- Client/transport DTO code generation은 허용하지만 Domain Entity·Use Case·Repository generation은 금지한다.
- Spring Data automatic GraphQL repository exposure는 allowlist compatibility module로만 제공한다.
- RSocket, HTTP GET, Incremental Delivery는 승격 ADR 전까지 Experimental이다.
- Advanced capability가 Stable HTTP POST, error, security, cost, DataLoader와 observability guardrail을 우회해서는 안 된다.
- 모든 task는 red-green TDD와 독립 commit으로 끝난다.
Advanced Module Map
modules/graphql-advanced/
├── graphql-advanced-bootstrap
├── graphql-persisted-operation
├── graphql-websocket
├── graphql-subscription
├── graphql-sse
├── graphql-dataloader-chaining
├── graphql-federation
├── graphql-codegen
├── graphql-spring-data-compat
├── graphql-rsocket
├── graphql-http-draft
└── graphql-incremental-delivery
Capability Classification
| Capability | Initial grade | Promotion evidence |
|---|---|---|
| Persisted Operation | Advanced Stable | durable registry, block propagation, schema/usage gate |
| WebSocket Subscription | Advanced Stable | auth, backpressure, soak, cancellation, drain |
| SSE Subscription | Advanced | HTTP/2 connection scale, proxy behavior, auth |
| Messaging-backed Replay | Advanced Extension | snapshot/live gap, history loss, authorization |
| Chained DataLoader | Advanced | dispatch/query-count regression |
| Federation Subgraph | Advanced | composition, router integration, failure/latency |
| Client Codegen | Optional Stable Tooling | generated-source compatibility |
| Spring Data Compat | Restricted Advanced | allowlist, projection, pagination/query guard |
| RSocket | Experimental | explicit consumers and transport evidence |
| HTTP GET | Experimental | draft, cache, CSRF and URL disclosure evidence |
| Incremental Delivery | Experimental | engine/transport interoperability and client negotiation |
Delivery Phases
| Phase | Tasks | Result |
|---|---|---|
| Boundary | 1 | Stable/Advanced dependency and feature flag isolation |
| Persisted Operations | 2–4 | Registry, execution lookup and G4 admin |
| Live Transport | 5–10 | WebSocket, auth, backpressure, ordering, SSE, replay |
| Data·Schema Extensions | 11–15 | Chained loader, Federation, Codegen, Spring Data compat |
| Experimental Transport | 16–18 | RSocket, HTTP GET, Incremental Delivery |
| Promotion | 19 | Capability-specific release and promotion gate |
Task 1: Advanced Module Boundary와 Feature Flag
Files:
- Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedCapability.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedFeatureFlags.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedModuleGuard.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedDependencyRules.java - Test:
modules/graphql-advanced/graphql-advanced-bootstrap/src/test/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedModuleGuardTest.java
Interfaces:
- Consumes: Stable GraphQL API 실행 플랫폼의 public contract와 Spring Boot environment.
- Produces: Advanced·Experimental 모듈이 Stable starter에 자동 유입되지 않도록 하는 dependency·feature flag 경계.
Implementation requirements:
-
모든 Advanced capability는
backend.graphql.advanced.*아래의 명시적 flag를 요구한다. -
Stable starter는 advanced module에 compile/runtime dependency를 갖지 않는다.
-
Advanced module은 Stable public types를 소비할 수 있지만 Stable module을 수정하지 않는다.
-
Experimental capability는 production에서 별도 승인 profile 없이는 시작되지 않는다.
-
Capability 상태는
ADVANCED_STABLE,EXPERIMENTAL,DISABLED로 구분한다. -
Step 1: Write the failing test
class GraphQlAdvancedModuleGuardTest {
@org.junit.jupiter.api.Test
void disabledCapabilityCannotStart() {
var flags = GraphQlAdvancedFeatureFlags.disabled();
var guard = new GraphQlAdvancedModuleGuard(flags);
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> guard.requireEnabled(
GraphQlAdvancedCapability.PERSISTED_OPERATION))
.isInstanceOf(
GraphQlAdvancedCapabilityDisabledException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-advanced-bootstrap:test --tests 'io.backend.skeleton.graphql.advanced.bootstrap.GraphQlAdvancedModuleGuardTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public enum GraphQlAdvancedCapability {
PERSISTED_OPERATION,
WEBSOCKET_SUBSCRIPTION,
SSE_SUBSCRIPTION,
FEDERATION_SUBGRAPH,
CODE_GENERATION,
SPRING_DATA_COMPAT,
RSOCKET,
HTTP_GET,
INCREMENTAL_DELIVERY
}
public record GraphQlAdvancedFeatureFlags(
java.util.Set<GraphQlAdvancedCapability> enabled) {
public static GraphQlAdvancedFeatureFlags disabled() {
return new GraphQlAdvancedFeatureFlags(
java.util.Set.of());
}
public boolean isEnabled(
GraphQlAdvancedCapability capability) {
return enabled.contains(capability);
}
}
public final class GraphQlAdvancedModuleGuard {
private final GraphQlAdvancedFeatureFlags flags;
public GraphQlAdvancedModuleGuard(
GraphQlAdvancedFeatureFlags flags) {
this.flags = flags;
}
public void requireEnabled(
GraphQlAdvancedCapability capability) {
if (!flags.isEnabled(capability)) {
throw new GraphQlAdvancedCapabilityDisabledException(
capability.name());
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-advanced-bootstrap:test --tests 'io.backend.skeleton.graphql.advanced.bootstrap.GraphQlAdvancedModuleGuardTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedCapability.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedFeatureFlags.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedModuleGuard.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedDependencyRules.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/test/java/io/backend/skeleton/graphql/advanced/bootstrap/GraphQlAdvancedModuleGuardTest.java'
git commit -m "build: isolate graphql advanced modules"
Task 2: Persisted Operation Model과 Registry
Files:
- Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationId.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperation.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationStatus.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationRegistry.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/InMemoryGraphQlPersistedOperationRegistry.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationConflictException.java - Test:
modules/graphql-advanced/graphql-persisted-operation/src/test/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationRegistryTest.java
Interfaces:
- Consumes: Stable schema contract hash, operation name, canonical document hash와 client profile.
- Produces: 승인된 operation document의 versioned registry와 ACTIVE·DEPRECATED·BLOCKED lifecycle.
Implementation requirements:
-
Registry record에 operation ID, operation name, SHA-256 document hash, canonical document, schema hash, allowed client profiles, maximum complexity와 variable bytes를 보존한다.
-
같은 operation ID로 다른 document를 등록하면 conflict다.
-
BLOCKED operation은 cache에 남아 있어도 실행되지 않는다.
-
Raw variables와 credential을 registry에 저장하지 않는다.
-
Persistent implementation SPI를 제공하되 Stable DB 선택을 강제하지 않는다.
-
Step 1: Write the failing test
class GraphQlPersistedOperationRegistryTest {
@org.junit.jupiter.api.Test
void sameIdWithDifferentDocumentIsConflict() {
var registry =
new InMemoryGraphQlPersistedOperationRegistry();
registry.register(GraphQlPersistedOperation.active(
"get-order-v1", "GetOrder", "sha256:a",
"query GetOrder { order { id } }", "schema-a"));
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> registry.register(
GraphQlPersistedOperation.active(
"get-order-v1", "GetOrder", "sha256:b",
"query GetOrder { order { status } }",
"schema-a")))
.isInstanceOf(
GraphQlPersistedOperationConflictException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-persisted-operation:test --tests 'io.backend.skeleton.graphql.advanced.persisted.GraphQlPersistedOperationRegistryTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public enum GraphQlPersistedOperationStatus {
ACTIVE,
DEPRECATED,
BLOCKED
}
public record GraphQlPersistedOperation(
GraphQlPersistedOperationId id,
String operationName,
String documentHash,
String canonicalDocument,
String schemaContractHash,
java.util.Set<String> allowedClientProfiles,
long maximumComplexity,
int maximumVariablesBytes,
GraphQlPersistedOperationStatus status) {
public static GraphQlPersistedOperation active(
String id,
String operationName,
String documentHash,
String canonicalDocument,
String schemaHash) {
return new GraphQlPersistedOperation(
new GraphQlPersistedOperationId(id),
operationName, documentHash, canonicalDocument,
schemaHash, java.util.Set.of("FIRST_PARTY"),
10_000, 65_536,
GraphQlPersistedOperationStatus.ACTIVE);
}
}
public interface GraphQlPersistedOperationRegistry {
void register(GraphQlPersistedOperation operation);
java.util.Optional<GraphQlPersistedOperation> find(
GraphQlPersistedOperationId id);
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-persisted-operation:test --tests 'io.backend.skeleton.graphql.advanced.persisted.GraphQlPersistedOperationRegistryTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationId.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperation.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationStatus.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationRegistry.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/InMemoryGraphQlPersistedOperationRegistry.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationConflictException.java' 'modules/graphql-advanced/graphql-persisted-operation/src/test/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationRegistryTest.java'
git commit -m "feat: add graphql persisted operation registry"
Task 3: Persisted Operation Lookup과 Execution Interceptor
Files:
- Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationRequest.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationLookup.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationInterceptor.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationPolicy.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationRejectedException.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedPreparsedBridge.java - Test:
modules/graphql-advanced/graphql-persisted-operation/src/test/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationInterceptorTest.java
Interfaces:
- Consumes: Persisted registry, Stable request context, client policy, schema hash와 preparsed cache.
- Produces: Operation ID로 canonical document를 복원하고 client·schema·variables·complexity를 재검증하는 interceptor.
Implementation requirements:
-
Client가 operation ID와 임의 query를 동시에 보내면 hash 일치 여부를 검증하거나 profile에 따라 거부한다.
-
Registry의 allowed client profile과 현재 client가 일치해야 한다.
-
Schema contract hash가 현재 schema와 맞지 않으면 실행하지 않는다.
-
Persisted record의 maximum complexity·variables limit와 현재 client policy 중 더 엄격한 값을 사용한다.
-
Lookup 성공이 authorization 성공을 의미하지 않는다.
-
Step 1: Write the failing test
class GraphQlPersistedOperationInterceptorTest {
@org.junit.jupiter.api.Test
void blockedOperationIsRejectedBeforeExecution() {
var operation = GraphQlPersistedOperation.active(
"dangerous-v1", "Dangerous", "sha256:a",
"query Dangerous { expensive }", "schema-a");
operation = new GraphQlPersistedOperation(
operation.id(), operation.operationName(),
operation.documentHash(), operation.canonicalDocument(),
operation.schemaContractHash(),
operation.allowedClientProfiles(),
operation.maximumComplexity(),
operation.maximumVariablesBytes(),
GraphQlPersistedOperationStatus.BLOCKED);
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> GraphQlPersistedOperationPolicy.requireActive(
operation))
.isInstanceOf(
GraphQlPersistedOperationRejectedException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-persisted-operation:test --tests 'io.backend.skeleton.graphql.advanced.persisted.GraphQlPersistedOperationInterceptorTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public final class GraphQlPersistedOperationPolicy {
public static GraphQlPersistedOperation requireActive(
GraphQlPersistedOperation operation) {
if (operation.status()
!= GraphQlPersistedOperationStatus.ACTIVE) {
throw new GraphQlPersistedOperationRejectedException(
"persisted operation is not active");
}
return operation;
}
private GraphQlPersistedOperationPolicy() {}
}
public record GraphQlPersistedOperationRequest(
GraphQlPersistedOperationId operationId,
String suppliedDocumentHash,
int variablesBytes) {
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-persisted-operation:test --tests 'io.backend.skeleton.graphql.advanced.persisted.GraphQlPersistedOperationInterceptorTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationRequest.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationLookup.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationInterceptor.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationPolicy.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationRejectedException.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedPreparsedBridge.java' 'modules/graphql-advanced/graphql-persisted-operation/src/test/java/io/backend/skeleton/graphql/advanced/persisted/GraphQlPersistedOperationInterceptorTest.java'
git commit -m "feat: execute registered graphql operations"
Task 4: G4 Persisted Operation Admin·Usage·Block Plane
Files:
- Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationAdminService.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationUsage.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationAudit.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationBlockCommand.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationRemovalGate.java - Create:
modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationAdminAuthorization.java - Test:
modules/graphql-advanced/graphql-persisted-operation/src/test/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationRemovalGateTest.java
Interfaces:
- Consumes: Persisted registry, schema usage observation, admin actor·audit port.
- Produces: Operation 등록·deprecate·incident block·remove를 감사 가능한 G4 관리 작업으로 제공.
Implementation requirements:
-
일반 GraphQL resolver나 application credential은 admin service에 접근하지 않는다.
-
Block는 즉시 실행 경로에 반영되며 cache를 우회하지 않는다.
-
Removal은 최근 usage, schema compatibility와 owner 승인 evidence를 요구한다.
-
모든 변경에 operator, reason, before/after, timestamp와 trace ID를 감사한다.
-
Raw variables와 credential은 audit에 포함하지 않는다.
-
Step 1: Write the failing test
class GraphQlPersistedOperationRemovalGateTest {
@org.junit.jupiter.api.Test
void recentUsageBlocksRemoval() {
var gate = new GraphQlPersistedOperationRemovalGate(
java.time.Duration.ofDays(30));
var usage = new GraphQlPersistedOperationUsage(
java.time.Instant.now().minus(
java.time.Duration.ofDays(1)), 12);
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> gate.verify(usage, java.time.Instant.now()))
.isInstanceOf(
GraphQlPersistedOperationRemovalRejectedException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-persisted-operation:test --tests 'io.backend.skeleton.graphql.advanced.admin.GraphQlPersistedOperationRemovalGateTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public record GraphQlPersistedOperationUsage(
java.time.Instant lastUsedAt,
long executions) {
}
public final class GraphQlPersistedOperationRemovalGate {
private final java.time.Duration quietPeriod;
public GraphQlPersistedOperationRemovalGate(
java.time.Duration quietPeriod) {
this.quietPeriod = quietPeriod;
}
public void verify(
GraphQlPersistedOperationUsage usage,
java.time.Instant now) {
if (usage.executions() > 0
&& usage.lastUsedAt().plus(quietPeriod)
.isAfter(now)) {
throw new
GraphQlPersistedOperationRemovalRejectedException(
"persisted operation used within quiet period");
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-persisted-operation:test --tests 'io.backend.skeleton.graphql.advanced.admin.GraphQlPersistedOperationRemovalGateTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationAdminService.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationUsage.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationAudit.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationBlockCommand.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationRemovalGate.java' 'modules/graphql-advanced/graphql-persisted-operation/src/main/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationAdminAuthorization.java' 'modules/graphql-advanced/graphql-persisted-operation/src/test/java/io/backend/skeleton/graphql/advanced/admin/GraphQlPersistedOperationRemovalGateTest.java'
git commit -m "feat: add graphql operation admin plane"
Task 5: graphql-transport-ws WebSocket Protocol Adapter
Files:
- Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketProtocol.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketProperties.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketHandlerFactory.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketLifecycle.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketConnectionId.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketProtocolError.java - Test:
modules/graphql-advanced/graphql-websocket/src/test/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketProtocolTest.java
Interfaces:
- Consumes: Spring GraphQL WebSocket handler, Stable execution service와 advanced feature guard.
- Produces:
graphql-transport-wsconnection_init·subscribe·complete lifecycle와 bounded connection policy.
Implementation requirements:
-
과거
subscriptions-transport-ws를 지원 protocol로 광고하지 않는다. -
connection init timeout, idle timeout, maximum connection age와 maximum subscriptions per connection을 설정한다.
-
Protocol error는 해당 operation 또는 connection scope에 맞게 종료한다.
-
서버 shutdown에서 신규 subscribe를 거부하고 기존 stream을 drain한다.
-
Connection ID와 operation ID를 metric label로 사용하지 않는다.
-
Step 1: Write the failing test
class GraphQlWebSocketProtocolTest {
@org.junit.jupiter.api.Test
void connectionInitAfterDeadlineIsRejected() {
var lifecycle = new GraphQlWebSocketLifecycle(
java.time.Duration.ofSeconds(5),
java.time.Instant.parse("2026-08-12T00:00:00Z"));
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> lifecycle.onConnectionInit(
java.time.Instant.parse(
"2026-08-12T00:00:06Z")))
.isInstanceOf(GraphQlWebSocketProtocolError.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-websocket:test --tests 'io.backend.skeleton.graphql.advanced.websocket.GraphQlWebSocketProtocolTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public enum GraphQlWebSocketProtocol {
GRAPHQL_TRANSPORT_WS("graphql-transport-ws");
private final String subProtocol;
GraphQlWebSocketProtocol(String subProtocol) {
this.subProtocol = subProtocol;
}
public String subProtocol() {
return subProtocol;
}
}
public final class GraphQlWebSocketLifecycle {
private final java.time.Duration initTimeout;
private final java.time.Instant connectedAt;
public GraphQlWebSocketLifecycle(
java.time.Duration initTimeout,
java.time.Instant connectedAt) {
this.initTimeout = initTimeout;
this.connectedAt = connectedAt;
}
public void onConnectionInit(java.time.Instant now) {
if (now.isAfter(connectedAt.plus(initTimeout))) {
throw new GraphQlWebSocketProtocolError(
"connection_init timeout");
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-websocket:test --tests 'io.backend.skeleton.graphql.advanced.websocket.GraphQlWebSocketProtocolTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketProtocol.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketProperties.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketHandlerFactory.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketLifecycle.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketConnectionId.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketProtocolError.java' 'modules/graphql-advanced/graphql-websocket/src/test/java/io/backend/skeleton/graphql/advanced/websocket/GraphQlWebSocketProtocolTest.java'
git commit -m "feat: add graphql websocket protocol"
Task 6: WebSocket Authentication Expiry·Revocation Lifecycle
Files:
- Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketAuthenticationInterceptor.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketPrincipal.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketCredentialExpiry.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketRevocationSignal.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlSubscriptionAuthorizationPolicy.java - Create:
modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketCloseReason.java - Test:
modules/graphql-advanced/graphql-websocket/src/test/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketAuthenticationInterceptorTest.java
Interfaces:
- Consumes: connection_init payload, security adapter, actor·tenant context, credential expiry와 revocation signal.
- Produces: Connection과 subscription에 actor context를 고정하고 expiry·revocation 시 fail-closed 종료하는 lifecycle.
Implementation requirements:
-
connection_init payload의 credential을 metric·log에 남기지 않는다.
-
인증된 actor·tenant를 이후 operation context에 전달한다.
-
Credential expiry가 오면 connection을 종료하고 silent refresh를 임의 구현하지 않는다.
-
Resource 권한이 변할 수 있는 sensitive subscription은 event delivery 전 재검증 profile을 지원한다.
-
한 connection에서 tenant context를 operation마다 바꿀 수 없다.
-
Step 1: Write the failing test
class GraphQlWebSocketAuthenticationInterceptorTest {
@org.junit.jupiter.api.Test
void expiredCredentialClosesConnection() {
var expiry = new GraphQlWebSocketCredentialExpiry(
java.time.Instant.parse("2026-08-12T00:00:00Z"));
org.assertj.core.api.Assertions.assertThat(
expiry.isExpired(
java.time.Instant.parse(
"2026-08-12T00:00:01Z")))
.isTrue();
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-websocket:test --tests 'io.backend.skeleton.graphql.advanced.security.GraphQlWebSocketAuthenticationInterceptorTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public record GraphQlWebSocketCredentialExpiry(
java.time.Instant expiresAt) {
public boolean isExpired(java.time.Instant now) {
return !now.isBefore(expiresAt);
}
}
public record GraphQlWebSocketPrincipal(
String actorFingerprint,
String tenantFingerprint,
java.time.Instant expiresAt) {
}
public enum GraphQlWebSocketCloseReason {
AUTHENTICATION_FAILED,
CREDENTIAL_EXPIRED,
AUTHORIZATION_REVOKED,
SERVER_DRAINING
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-websocket:test --tests 'io.backend.skeleton.graphql.advanced.security.GraphQlWebSocketAuthenticationInterceptorTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketAuthenticationInterceptor.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketPrincipal.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketCredentialExpiry.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketRevocationSignal.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlSubscriptionAuthorizationPolicy.java' 'modules/graphql-advanced/graphql-websocket/src/main/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketCloseReason.java' 'modules/graphql-advanced/graphql-websocket/src/test/java/io/backend/skeleton/graphql/advanced/security/GraphQlWebSocketAuthenticationInterceptorTest.java'
git commit -m "feat: secure graphql websocket lifecycle"
Task 7: Subscription Source SPI와 Bounded Backpressure
Files:
- Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionSource.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionEvent.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionContext.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionBufferPolicy.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSlowConsumerPolicy.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionDispatcher.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionTermination.java - Test:
modules/graphql-advanced/graphql-subscription/src/test/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionBufferPolicyTest.java
Interfaces:
- Consumes: Messaging/Application Publisher source, actor context, Reactor Publisher와 WebSocket/SSE transport.
- Produces: 내구성 보장을 과장하지 않는 bounded live stream과 slow-consumer termination.
Implementation requirements:
-
Messaging ACK·offset·DLQ·replay를 GraphQL subscription이 재구현하지 않는다.
-
Default slow-consumer 정책은 silent drop이 아니라 connection/operation 종료다.
-
Maximum buffered events와 maximum event bytes를 설정한다.
-
Subscriber cancel이 upstream source에 전파된다.
-
Integration Event를 GraphQL DTO로 변환하고 원본 broker message를 그대로 노출하지 않는다.
-
Step 1: Write the failing test
class GraphQlSubscriptionBufferPolicyTest {
@org.junit.jupiter.api.Test
void defaultPolicyTerminatesSlowConsumer() {
var policy = GraphQlSubscriptionBufferPolicy.defaultPolicy();
org.assertj.core.api.Assertions.assertThat(
policy.slowConsumerPolicy())
.isEqualTo(
GraphQlSlowConsumerPolicy.TERMINATE);
org.assertj.core.api.Assertions.assertThat(
policy.maximumBufferedEvents()).isGreaterThan(0);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-subscription:test --tests 'io.backend.skeleton.graphql.advanced.subscription.GraphQlSubscriptionBufferPolicyTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public enum GraphQlSlowConsumerPolicy {
TERMINATE,
DROP_ALLOWED_FOR_TELEMETRY
}
public record GraphQlSubscriptionBufferPolicy(
int maximumBufferedEvents,
long maximumBufferedBytes,
GraphQlSlowConsumerPolicy slowConsumerPolicy) {
public static GraphQlSubscriptionBufferPolicy defaultPolicy() {
return new GraphQlSubscriptionBufferPolicy(
128, 1_048_576,
GraphQlSlowConsumerPolicy.TERMINATE);
}
}
public interface GraphQlSubscriptionSource<E> {
org.reactivestreams.Publisher<E> open(
GraphQlSubscriptionContext context);
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-subscription:test --tests 'io.backend.skeleton.graphql.advanced.subscription.GraphQlSubscriptionBufferPolicyTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionSource.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionEvent.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionContext.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionBufferPolicy.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSlowConsumerPolicy.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionDispatcher.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionTermination.java' 'modules/graphql-advanced/graphql-subscription/src/test/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionBufferPolicyTest.java'
git commit -m "feat: add bounded graphql subscriptions"
Task 8: Subscription Ordering·Cancellation·Shutdown Profiles
Files:
- Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionOrderingProfile.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionExecutionPolicy.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionCancellation.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionDrainCoordinator.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionState.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionMetrics.java - Test:
modules/graphql-advanced/graphql-subscription/src/test/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionExecutionPolicyTest.java
Interfaces:
- Consumes: Subscription source, GraphQL Java subscription execution strategy와 transport lifecycle.
- Produces:
LOW_LATENCY·ORDEREDprofile, cancellation과 graceful shutdown drain.
Implementation requirements:
-
ORDERED는 source order를 보존하지만 head-of-line blocking과 buffer 비용을 명시한다. -
LOW_LATENCY는 completion 순서가 source 순서와 다를 수 있음을 계약한다. -
Ordering profile은 operation catalog에 등록한다.
-
Shutdown에서 신규 subscription을 거부하고 bounded time 동안 기존 subscription을 drain한다.
-
Cancellation은 resolver nested publisher와 upstream source 모두에 전파한다.
-
Step 1: Write the failing test
class GraphQlSubscriptionExecutionPolicyTest {
@org.junit.jupiter.api.Test
void orderedProfileRequestsGraphQlJavaOrderingFlag() {
var policy = GraphQlSubscriptionExecutionPolicy.ordered();
org.assertj.core.api.Assertions.assertThat(
policy.profile())
.isEqualTo(
GraphQlSubscriptionOrderingProfile.ORDERED);
org.assertj.core.api.Assertions.assertThat(
policy.keepSourceOrder()).isTrue();
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-subscription:test --tests 'io.backend.skeleton.graphql.advanced.subscription.GraphQlSubscriptionExecutionPolicyTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public enum GraphQlSubscriptionOrderingProfile {
LOW_LATENCY,
ORDERED
}
public record GraphQlSubscriptionExecutionPolicy(
GraphQlSubscriptionOrderingProfile profile,
boolean keepSourceOrder) {
public static GraphQlSubscriptionExecutionPolicy ordered() {
return new GraphQlSubscriptionExecutionPolicy(
GraphQlSubscriptionOrderingProfile.ORDERED, true);
}
public static GraphQlSubscriptionExecutionPolicy lowLatency() {
return new GraphQlSubscriptionExecutionPolicy(
GraphQlSubscriptionOrderingProfile.LOW_LATENCY,
false);
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-subscription:test --tests 'io.backend.skeleton.graphql.advanced.subscription.GraphQlSubscriptionExecutionPolicyTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionOrderingProfile.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionExecutionPolicy.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionCancellation.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionDrainCoordinator.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionState.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionMetrics.java' 'modules/graphql-advanced/graphql-subscription/src/test/java/io/backend/skeleton/graphql/advanced/subscription/GraphQlSubscriptionExecutionPolicyTest.java'
git commit -m "feat: add graphql subscription ordering profiles"
Task 9: SSE Distinct Connection Subscription Adapter
Files:
- Create:
modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseProperties.java - Create:
modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseHandlerFactory.java - Create:
modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseConnectionPolicy.java - Create:
modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseHeartbeat.java - Create:
modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseTermination.java - Test:
modules/graphql-advanced/graphql-sse/src/test/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseConnectionPolicyTest.java
Interfaces:
- Consumes: Spring
GraphQlSseHandler, Stable execution service, subscription buffer and auth policy. - Produces: POST JSON +
Accept: text/event-stream기반 Distinct Connection subscription transport.
Implementation requirements:
-
SSE를 Query·Mutation의 일반 response mode로 사용하지 않고 subscription-only로 제한한다.
-
Connection당 하나의 subscription이라는 Distinct Connection 특성을 문서·metric에 반영한다.
-
Heartbeat, idle timeout, maximum duration과 proxy buffering 요구를 설정한다.
-
WebSocket과 동일한 actor·tenant·authorization·cost policy를 사용한다.
-
HTTP/2 사용 여부와 연결 규모를 load test로 검증한다.
-
Step 1: Write the failing test
class GraphQlSseConnectionPolicyTest {
@org.junit.jupiter.api.Test
void acceptsOnlySubscriptionOperation() {
var policy = GraphQlSseConnectionPolicy.standard();
org.assertj.core.api.Assertions.assertThat(
policy.supports("subscription")).isTrue();
org.assertj.core.api.Assertions.assertThat(
policy.supports("query")).isFalse();
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-sse:test --tests 'io.backend.skeleton.graphql.advanced.sse.GraphQlSseConnectionPolicyTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public final class GraphQlSseConnectionPolicy {
public static GraphQlSseConnectionPolicy standard() {
return new GraphQlSseConnectionPolicy();
}
public boolean supports(String operationType) {
return "subscription".equals(operationType);
}
}
public record GraphQlSseProperties(
java.time.Duration heartbeatInterval,
java.time.Duration idleTimeout,
java.time.Duration maximumDuration) {
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-sse:test --tests 'io.backend.skeleton.graphql.advanced.sse.GraphQlSseConnectionPolicyTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseProperties.java' 'modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseHandlerFactory.java' 'modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseConnectionPolicy.java' 'modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseHeartbeat.java' 'modules/graphql-advanced/graphql-sse/src/main/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseTermination.java' 'modules/graphql-advanced/graphql-sse/src/test/java/io/backend/skeleton/graphql/advanced/sse/GraphQlSseConnectionPolicyTest.java'
git commit -m "feat: add graphql sse subscription transport"
Task 10: Messaging-backed Subscription Replay Extension
Files:
- Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlSubscriptionCursor.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlReplayPosition.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlReplaySource.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlSnapshotLiveHandoff.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlReplayAuthorization.java - Create:
modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlReplayGapException.java - Test:
modules/graphql-advanced/graphql-subscription/src/test/java/io/backend/skeleton/graphql/advanced/replay/GraphQlSnapshotLiveHandoffTest.java
Interfaces:
- Consumes: Messaging replay/offset capability, snapshot query, actor authorization와 signed cursor.
- Produces: GraphQL 표준 보장과 분리된 snapshot + live handoff 및 explicit replay cursor extension.
Implementation requirements:
-
GraphQL Core에 resume 표준이 있다고 광고하지 않는다.
-
Replay durability와 offset 보장은 Messaging platform이 소유한다.
-
Snapshot sequence와 live source 시작 position 사이에 gap·duplicate가 없도록 handoff를 검증한다.
-
Cursor는 actor/client·subscription profile에 bind하고 서명한다.
-
Replay window 밖의 cursor는 history-lost error로 반환한다.
-
Step 1: Write the failing test
class GraphQlSnapshotLiveHandoffTest {
@org.junit.jupiter.api.Test
void gapBetweenSnapshotAndLivePositionIsRejected() {
var handoff = new GraphQlSnapshotLiveHandoff(
new GraphQlReplayPosition(10),
new GraphQlReplayPosition(12));
org.assertj.core.api.Assertions.assertThatThrownBy(
handoff::verifyContiguous)
.isInstanceOf(GraphQlReplayGapException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-subscription:test --tests 'io.backend.skeleton.graphql.advanced.replay.GraphQlSnapshotLiveHandoffTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public record GraphQlReplayPosition(long sequence) {
public GraphQlReplayPosition {
if (sequence < 0) {
throw new IllegalArgumentException(
"sequence cannot be negative");
}
}
}
public record GraphQlSnapshotLiveHandoff(
GraphQlReplayPosition snapshotPosition,
GraphQlReplayPosition liveStartPosition) {
public void verifyContiguous() {
if (liveStartPosition.sequence()
> snapshotPosition.sequence() + 1) {
throw new GraphQlReplayGapException(
"snapshot and live stream contain a gap");
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-subscription:test --tests 'io.backend.skeleton.graphql.advanced.replay.GraphQlSnapshotLiveHandoffTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlSubscriptionCursor.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlReplayPosition.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlReplaySource.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlSnapshotLiveHandoff.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlReplayAuthorization.java' 'modules/graphql-advanced/graphql-subscription/src/main/java/io/backend/skeleton/graphql/advanced/replay/GraphQlReplayGapException.java' 'modules/graphql-advanced/graphql-subscription/src/test/java/io/backend/skeleton/graphql/advanced/replay/GraphQlSnapshotLiveHandoffTest.java'
git commit -m "feat: add graphql subscription replay extension"
Task 11: GraphQL Java 25 Chained DataLoader Opt-in
Files:
- Create:
modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlChainedDataLoaderPolicy.java - Create:
modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlDataLoaderDependencyGraph.java - Create:
modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlDataLoaderCycleDetector.java - Create:
modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlChainedDispatchConfigurer.java - Create:
modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlChainedLoaderMetrics.java - Test:
modules/graphql-advanced/graphql-dataloader-chaining/src/test/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlDataLoaderCycleDetectorTest.java
Interfaces:
- Consumes: Stable request-scoped DataLoader registry와 GraphQL Java 25 chained dispatch capability.
- Produces: 명시적 dependency graph를 가진 opt-in chained loader execution과 cycle·dispatch regression gate.
Implementation requirements:
-
Stable default에서는 chained dispatch를 활성화하지 않는다.
-
각 loader dependency를 manifest에 선언한다.
-
Dependency cycle이 있으면 startup 실패다.
-
Dispatch 순서 변경이 query count·batch size·result ordering에 미치는 영향을 contract test로 검증한다.
-
Request scope, actor·tenant isolation과 maximum batch size 규칙을 그대로 유지한다.
-
Step 1: Write the failing test
class GraphQlDataLoaderCycleDetectorTest {
@org.junit.jupiter.api.Test
void detectsLoaderDependencyCycle() {
var graph = new GraphQlDataLoaderDependencyGraph()
.dependsOn("orders", "customers")
.dependsOn("customers", "orders");
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> new GraphQlDataLoaderCycleDetector().verify(graph))
.isInstanceOf(
GraphQlDataLoaderDependencyCycleException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-dataloader-chaining:test --tests 'io.backend.skeleton.graphql.advanced.chaining.GraphQlDataLoaderCycleDetectorTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public final class GraphQlDataLoaderDependencyGraph {
private final java.util.Map<String, java.util.Set<String>> edges =
new java.util.LinkedHashMap<>();
public GraphQlDataLoaderDependencyGraph dependsOn(
String loader, String dependency) {
edges.computeIfAbsent(loader,
ignored -> new java.util.LinkedHashSet<>())
.add(dependency);
return this;
}
java.util.Map<String, java.util.Set<String>> edges() {
return java.util.Collections.unmodifiableMap(edges);
}
}
public record GraphQlChainedDataLoaderPolicy(
boolean enabled,
int maximumDepth) {
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-dataloader-chaining:test --tests 'io.backend.skeleton.graphql.advanced.chaining.GraphQlDataLoaderCycleDetectorTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlChainedDataLoaderPolicy.java' 'modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlDataLoaderDependencyGraph.java' 'modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlDataLoaderCycleDetector.java' 'modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlChainedDispatchConfigurer.java' 'modules/graphql-advanced/graphql-dataloader-chaining/src/main/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlChainedLoaderMetrics.java' 'modules/graphql-advanced/graphql-dataloader-chaining/src/test/java/io/backend/skeleton/graphql/advanced/chaining/GraphQlDataLoaderCycleDetectorTest.java'
git commit -m "feat: add opt in chained graphql dataloaders"
Task 12: Federation Subgraph Schema와 Entity Resolver
Files:
- Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationProperties.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationSchemaFactory.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationEntityKey.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationEntityResolver.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationBatchResolver.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationCapability.java - Test:
modules/graphql-advanced/graphql-federation/src/test/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationEntityResolverTest.java
Interfaces:
- Consumes: Stable SDL assembly, federation-jvm integration, Application query services와 DataLoader.
- Produces: 별도 opt-in Subgraph schema,
@keyentity mapping과 batch reference resolution.
Implementation requirements:
-
Single executable schema가 기본이며 federation flag가 없으면 federation wiring을 등록하지 않는다.
-
Entity reference resolver가 Repository를 직접 호출하지 않고 Application query service를 사용한다.
-
Entity key는 versioned contract이며 변경 시 breaking review를 요구한다.
-
Batch entity resolution은 요청 단위 DataLoader와 tenant context를 사용한다.
-
Federation router나 supergraph 운영은 이 모듈이 소유하지 않는다.
-
Step 1: Write the failing test
class GraphQlFederationEntityResolverTest {
@org.junit.jupiter.api.Test
void rejectsRepresentationMissingDeclaredKey() {
var key = new GraphQlFederationEntityKey(
"Order", java.util.List.of("id"));
var resolver = new GraphQlFederationEntityResolver(key);
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> resolver.validateRepresentation(
java.util.Map.of("__typename", "Order")))
.isInstanceOf(
GraphQlFederationRepresentationException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-federation:test --tests 'io.backend.skeleton.graphql.advanced.federation.GraphQlFederationEntityResolverTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public record GraphQlFederationEntityKey(
String typeName,
java.util.List<String> fields) {
public GraphQlFederationEntityKey {
fields = java.util.List.copyOf(fields);
if (fields.isEmpty()) {
throw new IllegalArgumentException(
"federation entity key cannot be empty");
}
}
}
public final class GraphQlFederationEntityResolver {
private final GraphQlFederationEntityKey key;
public GraphQlFederationEntityResolver(
GraphQlFederationEntityKey key) {
this.key = key;
}
public void validateRepresentation(
java.util.Map<String, Object> representation) {
for (String field : key.fields()) {
if (!representation.containsKey(field)) {
throw new
GraphQlFederationRepresentationException(
"missing federation entity key field");
}
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-federation:test --tests 'io.backend.skeleton.graphql.advanced.federation.GraphQlFederationEntityResolverTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationProperties.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationSchemaFactory.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationEntityKey.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationEntityResolver.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationBatchResolver.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationCapability.java' 'modules/graphql-advanced/graphql-federation/src/test/java/io/backend/skeleton/graphql/advanced/federation/GraphQlFederationEntityResolverTest.java'
git commit -m "feat: add graphql federation subgraph"
Task 13: Federation Composition·Deployment·Failure Gate
Files:
- Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlSubgraphContract.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationCompositionResult.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationCompositionGate.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationUsageReport.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationLatencyBudget.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationDeploymentOrder.java - Create:
modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationReleaseEvidence.java - Test:
modules/graphql-advanced/graphql-federation/src/test/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationCompositionGateTest.java
Interfaces:
- Consumes: Subgraph SDL, entity keys, router composition result, usage·latency·failure evidence.
- Produces: Composition CI, cross-subgraph dependency·latency·partial failure 검증과 배포 순서 gate.
Implementation requirements:
-
Composition 성공만으로 release를 승인하지 않고 entity key, owner, downstream dependency, latency budget을 검증한다.
-
Cross-subgraph N+1과 per-entity downstream call을 performance gate에서 탐지한다.
-
Subgraph schema가 router보다 먼저 또는 호환되지 않는 순서로 배포되지 않도록 deployment order를 검증한다.
-
부분 장애에서 nullable boundary와 error ownership을 contract test로 검증한다.
-
Router 설정과 운영은 별도 프로젝트의 evidence로 입력받는다.
-
Step 1: Write the failing test
class GraphQlFederationCompositionGateTest {
@org.junit.jupiter.api.Test
void compositionWithoutLatencyEvidenceIsRejected() {
var evidence = new GraphQlFederationReleaseEvidence(
true, true, false, true);
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> new GraphQlFederationCompositionGate()
.verify(evidence))
.isInstanceOf(
GraphQlFederationReleaseRejectedException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-federation:test --tests 'io.backend.skeleton.graphql.advanced.composition.GraphQlFederationCompositionGateTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public record GraphQlFederationReleaseEvidence(
boolean compositionPassed,
boolean entityContractsPassed,
boolean latencyPassed,
boolean failureContractsPassed) {
}
public final class GraphQlFederationCompositionGate {
public void verify(
GraphQlFederationReleaseEvidence evidence) {
if (!evidence.compositionPassed()
|| !evidence.entityContractsPassed()
|| !evidence.latencyPassed()
|| !evidence.failureContractsPassed()) {
throw new
GraphQlFederationReleaseRejectedException(
"federation composition evidence incomplete");
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-federation:test --tests 'io.backend.skeleton.graphql.advanced.composition.GraphQlFederationCompositionGateTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlSubgraphContract.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationCompositionResult.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationCompositionGate.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationUsageReport.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationLatencyBudget.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationDeploymentOrder.java' 'modules/graphql-advanced/graphql-federation/src/main/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationReleaseEvidence.java' 'modules/graphql-advanced/graphql-federation/src/test/java/io/backend/skeleton/graphql/advanced/composition/GraphQlFederationCompositionGateTest.java'
git commit -m "chore: add graphql federation composition gate"
Task 14: Client·Transport DTO Code Generation
Files:
- Create:
modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlCodegenProfile.java - Create:
modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlScalarMapping.java - Create:
modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlGeneratedSourceBoundary.java - Create:
modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlClientOperationGenerator.java - Create:
modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlTransportTypeGenerator.java - Create:
modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlGeneratedCompatibilityGate.java - Test:
modules/graphql-advanced/graphql-codegen/src/test/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlGeneratedSourceBoundaryTest.java
Interfaces:
- Consumes: SDL, operation documents, scalar manifest와 selected codegen engine.
- Produces: Client request/response model과 transport-only type을 생성하되 domain·repository를 생성하지 않는 tooling.
Implementation requirements:
-
Domain Entity, Application Use Case interface와 Repository를 생성하지 않는다.
-
Generated source는 별도 directory·package에 두고 사람이 작성한 domain code를 덮어쓰지 않는다.
-
Custom scalar, nullability, union, interface와 oneOf mapping을 명시한다.
-
Operation document를 schema와 함께 compile-time 검증한다.
-
Schema additive change가 generated client의 exhaustive enum/union source compatibility를 깨는지 별도 보고한다.
-
Step 1: Write the failing test
class GraphQlGeneratedSourceBoundaryTest {
@org.junit.jupiter.api.Test
void domainAndRepositoryGenerationAreForbidden() {
var boundary = GraphQlGeneratedSourceBoundary.standard();
org.assertj.core.api.Assertions.assertThat(
boundary.isAllowed("CLIENT_RESPONSE")).isTrue();
org.assertj.core.api.Assertions.assertThat(
boundary.isAllowed("DOMAIN_ENTITY")).isFalse();
org.assertj.core.api.Assertions.assertThat(
boundary.isAllowed("REPOSITORY")).isFalse();
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-codegen:test --tests 'io.backend.skeleton.graphql.advanced.codegen.GraphQlGeneratedSourceBoundaryTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public final class GraphQlGeneratedSourceBoundary {
private static final java.util.Set<String> ALLOWED =
java.util.Set.of(
"CLIENT_REQUEST",
"CLIENT_RESPONSE",
"TRANSPORT_INPUT",
"TRANSPORT_OUTPUT");
public static GraphQlGeneratedSourceBoundary standard() {
return new GraphQlGeneratedSourceBoundary();
}
public boolean isAllowed(String generatedType) {
return ALLOWED.contains(generatedType);
}
}
public record GraphQlScalarMapping(
String scalarName,
String javaType,
String codecId) {
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-codegen:test --tests 'io.backend.skeleton.graphql.advanced.codegen.GraphQlGeneratedSourceBoundaryTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlCodegenProfile.java' 'modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlScalarMapping.java' 'modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlGeneratedSourceBoundary.java' 'modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlClientOperationGenerator.java' 'modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlTransportTypeGenerator.java' 'modules/graphql-advanced/graphql-codegen/src/main/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlGeneratedCompatibilityGate.java' 'modules/graphql-advanced/graphql-codegen/src/test/java/io/backend/skeleton/graphql/advanced/codegen/GraphQlGeneratedSourceBoundaryTest.java'
git commit -m "build: add graphql client code generation"
Task 15: Allowlisted Spring Data GraphQL Compatibility
Files:
- Create:
modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryExposure.java - Create:
modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryAllowlist.java - Create:
modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryArgumentPolicy.java - Create:
modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryPaginationPolicy.java - Create:
modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryProjectionPolicy.java - Create:
modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryExposureValidator.java - Test:
modules/graphql-advanced/graphql-spring-data-compat/src/test/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryExposureValidatorTest.java
Interfaces:
- Consumes: Spring
@GraphQlRepository, Querydsl/QBE repository, schema coordinate와 explicit allowlist. - Produces: 자동 DataFetcher 기능을 제한된 compatibility path로 제공하고 persistence model 자동 노출을 차단.
Implementation requirements:
-
등록되지 않은 repository는 GraphQL DataFetcher로 자동 노출하지 않는다.
-
허용 filter, sort, projection과 maximum page size를 coordinate별로 정의한다.
-
기본 offset pagination 20개 동작을 암묵적으로 사용하지 않고 explicit policy를 요구한다.
-
Entity/Document를 output으로 직접 반환하지 않고 승인된 projection만 허용한다.
-
Stable resolver·Application Service 경계를 대체하는 주류 API로 문서화하지 않는다.
-
Step 1: Write the failing test
class GraphQlRepositoryExposureValidatorTest {
@org.junit.jupiter.api.Test
void unregisteredRepositoryIsRejected() {
var validator =
new GraphQlRepositoryExposureValidator(
GraphQlRepositoryAllowlist.empty());
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> validator.verify(
new GraphQlRepositoryExposure(
"OrderRepository", "Query.orders")))
.isInstanceOf(
GraphQlRepositoryExposureRejectedException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-spring-data-compat:test --tests 'io.backend.skeleton.graphql.advanced.compat.GraphQlRepositoryExposureValidatorTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public record GraphQlRepositoryExposure(
String repositoryName,
String schemaCoordinate) {
}
public final class GraphQlRepositoryAllowlist {
private final java.util.Set<String> repositoryNames;
private GraphQlRepositoryAllowlist(
java.util.Set<String> repositoryNames) {
this.repositoryNames =
java.util.Set.copyOf(repositoryNames);
}
public static GraphQlRepositoryAllowlist empty() {
return new GraphQlRepositoryAllowlist(
java.util.Set.of());
}
public boolean contains(String repositoryName) {
return repositoryNames.contains(repositoryName);
}
}
public final class GraphQlRepositoryExposureValidator {
private final GraphQlRepositoryAllowlist allowlist;
public GraphQlRepositoryExposureValidator(
GraphQlRepositoryAllowlist allowlist) {
this.allowlist = allowlist;
}
public void verify(GraphQlRepositoryExposure exposure) {
if (!allowlist.contains(exposure.repositoryName())) {
throw new
GraphQlRepositoryExposureRejectedException(
exposure.repositoryName());
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-spring-data-compat:test --tests 'io.backend.skeleton.graphql.advanced.compat.GraphQlRepositoryExposureValidatorTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryExposure.java' 'modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryAllowlist.java' 'modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryArgumentPolicy.java' 'modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryPaginationPolicy.java' 'modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryProjectionPolicy.java' 'modules/graphql-advanced/graphql-spring-data-compat/src/main/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryExposureValidator.java' 'modules/graphql-advanced/graphql-spring-data-compat/src/test/java/io/backend/skeleton/graphql/advanced/compat/GraphQlRepositoryExposureValidatorTest.java'
git commit -m "feat: add graphql spring data compat guard"
Task 16: RSocket GraphQL Experimental Transport
Files:
- Create:
modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketProperties.java - Create:
modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketRoutePolicy.java - Create:
modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketHandlerFactory.java - Create:
modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketAuthentication.java - Create:
modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketErrorMapper.java - Create:
modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketCapability.java - Test:
modules/graphql-advanced/graphql-rsocket/src/test/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketRoutePolicyTest.java
Interfaces:
- Consumes: Spring GraphQL RSocket handler, Stable execution service와 experimental feature approval.
- Produces: 내부 시스템용 request-response Query/Mutation과 request-stream Subscription experimental transport.
Implementation requirements:
-
RSocket는 public default transport가 아니다.
-
Query·Mutation은 request-response, Subscription은 request-stream으로만 매핑한다.
-
Stable HTTP/WebSocket과 동일한 actor·tenant·cost·error policy를 적용한다.
-
Route·metadata MIME type·authentication을 allowlist한다.
-
Production 활성화에는 explicit consumer, load·failure evidence와 owner가 필요하다.
-
Step 1: Write the failing test
class GraphQlRSocketRoutePolicyTest {
@org.junit.jupiter.api.Test
void rejectsUnknownRoute() {
var policy = new GraphQlRSocketRoutePolicy(
java.util.Set.of("graphql"));
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> policy.requireAllowed("admin.raw"))
.isInstanceOf(
GraphQlRSocketRouteRejectedException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-rsocket:test --tests 'io.backend.skeleton.graphql.advanced.rsocket.GraphQlRSocketRoutePolicyTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public final class GraphQlRSocketRoutePolicy {
private final java.util.Set<String> allowedRoutes;
public GraphQlRSocketRoutePolicy(
java.util.Set<String> allowedRoutes) {
this.allowedRoutes =
java.util.Set.copyOf(allowedRoutes);
}
public String requireAllowed(String route) {
if (!allowedRoutes.contains(route)) {
throw new GraphQlRSocketRouteRejectedException(route);
}
return route;
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-rsocket:test --tests 'io.backend.skeleton.graphql.advanced.rsocket.GraphQlRSocketRoutePolicyTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketProperties.java' 'modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketRoutePolicy.java' 'modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketHandlerFactory.java' 'modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketAuthentication.java' 'modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketErrorMapper.java' 'modules/graphql-advanced/graphql-rsocket/src/main/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketCapability.java' 'modules/graphql-advanced/graphql-rsocket/src/test/java/io/backend/skeleton/graphql/advanced/rsocket/GraphQlRSocketRoutePolicyTest.java'
git commit -m "feat: add experimental graphql rsocket"
Task 17: GraphQL over HTTP GET Draft Profile
Files:
- Create:
modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetProfile.java - Create:
modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetRequestParser.java - Create:
modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetCachePolicy.java - Create:
modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetCsrfPolicy.java - Create:
modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetOperationPolicy.java - Create:
modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpDraftCompatibilityReport.java - Test:
modules/graphql-advanced/graphql-http-draft/src/test/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetOperationPolicyTest.java
Interfaces:
- Consumes: GraphQL over HTTP Stage 2 Draft, Stable HTTP V1 policy와 experimental flag.
- Produces: Query-only GET parsing, cache·CSRF 정책과 draft compatibility report.
Implementation requirements:
-
Mutation과 Subscription을 GET으로 허용하지 않는다.
-
Query, operationName, variables, extensions의 URI 크기와 encoding을 제한한다.
-
Cookie credential 사용 시 CSRF와 cache behavior를 명시적으로 검증한다.
-
Raw query와 variables가 access log, referer 또는 metric에 노출될 위험을 문서·테스트한다.
-
Draft 변화가 Stable POST contract를 변경하지 않는다.
-
Step 1: Write the failing test
class GraphQlHttpGetOperationPolicyTest {
@org.junit.jupiter.api.Test
void mutationOverGetIsRejected() {
var policy = GraphQlHttpGetOperationPolicy.queryOnly();
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> policy.verify("mutation"))
.isInstanceOf(
GraphQlHttpGetRejectedException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-http-draft:test --tests 'io.backend.skeleton.graphql.advanced.get.GraphQlHttpGetOperationPolicyTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public final class GraphQlHttpGetOperationPolicy {
public static GraphQlHttpGetOperationPolicy queryOnly() {
return new GraphQlHttpGetOperationPolicy();
}
public void verify(String operationType) {
if (!"query".equals(operationType)) {
throw new GraphQlHttpGetRejectedException(
"GET supports query operations only");
}
}
}
public record GraphQlHttpGetProfile(
int maximumUriBytes,
boolean sharedCacheAllowed,
boolean csrfRequired) {
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-http-draft:test --tests 'io.backend.skeleton.graphql.advanced.get.GraphQlHttpGetOperationPolicyTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetProfile.java' 'modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetRequestParser.java' 'modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetCachePolicy.java' 'modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetCsrfPolicy.java' 'modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetOperationPolicy.java' 'modules/graphql-advanced/graphql-http-draft/src/main/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpDraftCompatibilityReport.java' 'modules/graphql-advanced/graphql-http-draft/src/test/java/io/backend/skeleton/graphql/advanced/get/GraphQlHttpGetOperationPolicyTest.java'
git commit -m "feat: add experimental graphql http get"
Task 18: Incremental Delivery Experimental Profile
Files:
- Create:
modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalDeliveryCapability.java - Create:
modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalDeliveryProfile.java - Create:
modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalPatch.java - Create:
modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalTransportPolicy.java - Create:
modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalCancellation.java - Create:
modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalCompatibilityGate.java - Test:
modules/graphql-advanced/graphql-incremental-delivery/src/test/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalCompatibilityGateTest.java
Interfaces:
- Consumes: 실험 기능을 지원하는 GraphQL Java/Spring 조합, multipart/mixed 또는 streaming transport capability.
- Produces:
@defer·@stream계열 기능을 Stable contract와 분리한 version-gated experimental profile.
Implementation requirements:
-
September 2025 Stable schema contract에 존재하지 않거나 구현 조합이 안정화되지 않은 기능을 자동 활성화하지 않는다.
-
초기 result와 후속 patch의 error·path·ordering·cancellation 계약을 별도로 검증한다.
-
Stable HTTP response byte budget과 timeout을 우회하지 않는다.
-
Client capability negotiation 없이 incremental response를 반환하지 않는다.
-
Compatibility gate가 실패하면 일반 non-incremental execution으로 silent fallback하지 않고 설정 오류로 차단한다.
-
Step 1: Write the failing test
class GraphQlIncrementalCompatibilityGateTest {
@org.junit.jupiter.api.Test
void unsupportedRuntimeCannotEnableIncrementalDelivery() {
var gate = new GraphQlIncrementalCompatibilityGate();
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> gate.verify(
new GraphQlIncrementalDeliveryCapability(
false, false)))
.isInstanceOf(
GraphQlIncrementalDeliveryRejectedException.class);
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-incremental-delivery:test --tests 'io.backend.skeleton.graphql.advanced.incremental.GraphQlIncrementalCompatibilityGateTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public record GraphQlIncrementalDeliveryCapability(
boolean engineSupported,
boolean transportSupported) {
}
public final class GraphQlIncrementalCompatibilityGate {
public void verify(
GraphQlIncrementalDeliveryCapability capability) {
if (!capability.engineSupported()
|| !capability.transportSupported()) {
throw new
GraphQlIncrementalDeliveryRejectedException(
"incremental delivery runtime unsupported");
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-incremental-delivery:test --tests 'io.backend.skeleton.graphql.advanced.incremental.GraphQlIncrementalCompatibilityGateTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalDeliveryCapability.java' 'modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalDeliveryProfile.java' 'modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalPatch.java' 'modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalTransportPolicy.java' 'modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalCancellation.java' 'modules/graphql-advanced/graphql-incremental-delivery/src/main/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalCompatibilityGate.java' 'modules/graphql-advanced/graphql-incremental-delivery/src/test/java/io/backend/skeleton/graphql/advanced/incremental/GraphQlIncrementalCompatibilityGateTest.java'
git commit -m "feat: add experimental graphql incremental delivery"
Task 19: Advanced Capability Promotion·Soak·Release Gate
Files:
- Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedReleaseEvidence.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedReleaseGate.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedPromotionDecision.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedSoakScenario.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedCompatibilityMatrix.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedRunbookIndex.java - Create:
modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedReleaseFailure.java - Test:
modules/graphql-advanced/graphql-advanced-bootstrap/src/test/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedReleaseGateTest.java
Interfaces:
- Consumes: Stable Task 1–48 release evidence와 Advanced capability별 composition·soak·security·compatibility evidence.
- Produces: Advanced Stable 승격과 Experimental 유지·철회를 결정하는 capability별 release gate.
Implementation requirements:
-
Stable Task 1–48의 release gate가 통과하지 않으면 Advanced 계획을 시작하지 않는다.
-
WebSocket·SSE는 장기 connection soak, slow consumer, auth expiry, cancellation, shutdown drain evidence를 요구한다.
-
Federation은 composition, router integration, cross-subgraph latency와 partial failure evidence를 요구한다.
-
Persisted Operation은 registry durability, block propagation, usage와 schema compatibility evidence를 요구한다.
-
RSocket·HTTP GET·Incremental Delivery는 명시적 승격 ADR 전까지 Experimental로 남는다.
-
Step 1: Write the failing test
class GraphQlAdvancedReleaseGateTest {
@org.junit.jupiter.api.Test
void advancedCannotReleaseBeforeStableBaseline() {
var evidence = new GraphQlAdvancedReleaseEvidence(
false, true, true, true, true);
org.assertj.core.api.Assertions.assertThatThrownBy(
() -> new GraphQlAdvancedReleaseGate().verify(evidence))
.isInstanceOf(GraphQlAdvancedReleaseFailure.class)
.hasMessageContaining("stable");
}
}
- Step 2: Run the focused test and verify the failure
Run:
./gradlew :modules:graphql-advanced:graphql-advanced-bootstrap:test --tests 'io.backend.skeleton.graphql.advanced.release.GraphQlAdvancedReleaseGateTest'
Expected: FAIL because the production contract defined by this task does not exist or rejects the required invariant incorrectly.
- Step 3: Implement the smallest complete production contract
public record GraphQlAdvancedReleaseEvidence(
boolean stableBaselinePassed,
boolean capabilityContractsPassed,
boolean securityPassed,
boolean soakPassed,
boolean compatibilityPassed) {
}
public final class GraphQlAdvancedReleaseGate {
public void verify(GraphQlAdvancedReleaseEvidence evidence) {
if (!evidence.stableBaselinePassed()) {
throw new GraphQlAdvancedReleaseFailure(
"stable graphql baseline must pass first");
}
if (!evidence.capabilityContractsPassed()
|| !evidence.securityPassed()
|| !evidence.soakPassed()
|| !evidence.compatibilityPassed()) {
throw new GraphQlAdvancedReleaseFailure(
"advanced graphql evidence incomplete");
}
}
}
Implement all listed files with the exact public names and invariants above. Keep persistence, provider, credential and dynamic identifier types outside the public contract.
- Step 4: Run the focused test and the owning suite
Run:
./gradlew :modules:graphql-advanced:graphql-advanced-bootstrap:test --tests 'io.backend.skeleton.graphql.advanced.release.GraphQlAdvancedReleaseGateTest'
./gradlew graphqlAdvancedTest
Expected: PASS for the focused test and the aggregate suite.
- Step 5: Commit the independently reviewable change
git add 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedReleaseEvidence.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedReleaseGate.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedPromotionDecision.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedSoakScenario.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedCompatibilityMatrix.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedRunbookIndex.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/main/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedReleaseFailure.java' 'modules/graphql-advanced/graphql-advanced-bootstrap/src/test/java/io/backend/skeleton/graphql/advanced/release/GraphQlAdvancedReleaseGateTest.java'
git commit -m "chore: add graphql advanced release gate"