Files
clean-architecture-backend-…/docs/superpowers/plans/2026-08-12-graphql-advanced-capabilities-expansion-plan.md
T

103 KiB
Raw Blame History

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 148과 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 24 Registry, execution lookup and G4 admin
Live Transport 510 WebSocket, auth, backpressure, ordering, SSE, replay
Data·Schema Extensions 1115 Chained loader, Federation, Codegen, Spring Data compat
Experimental Transport 1618 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-ws connection_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·ORDERED profile, 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, @key entity 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 148 release evidence와 Advanced capability별 composition·soak·security·compatibility evidence.
  • Produces: Advanced Stable 승격과 Experimental 유지·철회를 결정하는 capability별 release gate.

Implementation requirements:

  • Stable Task 148의 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"