# DDD Naming Governance ReportType Loop 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:** Complete the second DDD naming governance loop by moving report type ownership into `domain`, keeping the public contracts enum stable, and removing the `exam-sprint-domain -> exam-sprint-contracts` dependency.

**Architecture:** `domain` owns the business category enum `ReportType`; `contracts` keeps the public API enum `ExamSprintReportType`; `application` maps explicitly between the two at API boundaries. `domain` model and domain ports use the domain enum, while response DTOs and runtime HTTP JSON continue to expose the existing contract enum names.

**Tech Stack:** Java 17, Maven multi-module build, Spring Boot 3.3.5, JUnit 5, AssertJ, ArchUnit, existing exam-sprint application/infrastructure/runtime tests.

---

> **Execution note:** Do not create git commits unless the user explicitly asks for commits. Treat each “commit checkpoint” as a local verification checkpoint until commit permission is given.

> **Execution note:** This plan is intentionally limited to `ReportType`. Do not migrate `JsonNode` payloads, do not move `Storage` / `Renderer` / `PdfGenerator`, do not split `DefaultExamSprintReportApplicationService`, and do not change HTTP endpoint paths, JSON field names, status codes, or public enum literals.

> **Initial worktree baseline:** The isolated worktree is `/Users/exiao/Codes/dcjxb.microservice/.worktrees/refactor-ddd-report-type-loop` on branch `refactor/ddd命名治理二轮-report-type`. Before writing this plan, `mvn -q test` was run in this worktree and completed without a tool-reported failure.

## Target File Structure

### Create

- `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ReportType.java`
  - Domain-owned report category enum with the same current literals as the public contract enum.

### Modify

- `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReport.java`
  - Change `reportType` field and `pending(...)` factory parameter from contract `ExamSprintReportType` to domain `ReportType`.
- `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReportRenderer.java`
  - Change `supports(...)` to accept domain `ReportType`; keep `JsonNode` payload unchanged.
- `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReportStorage.java`
  - Change `upload(...)` report type parameter to domain `ReportType`.
- `abilities/exam-sprint/domain/pom.xml`
  - Remove the `exam-sprint-contracts` dependency; keep `jackson-databind` because payload migration is out of scope.
- `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportContractMapper.java`
  - Add explicit `ReportType -> ExamSprintReportType` and `ExamSprintReportType -> ReportType` mapping methods.
- `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/DefaultExamSprintReportApplicationService.java`
  - Use domain `ReportType` internally and map report type back to contract enum when constructing response DTOs.
- `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportContractMapperTest.java`
  - Cover all report type mappings and null strategy.
- `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportApplicationServiceTest.java`
  - Assert domain entity type with domain `ReportType`; keep response assertions on contract `ExamSprintReportType`.
- `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportGenerationWorkerTest.java`
  - Update domain report construction and renderer/storage test doubles to use domain `ReportType`.
- `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/storage/InMemoryExamSprintReportStorage.java`
  - Implement the updated domain storage port using domain `ReportType`.
- `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/storage/AzureBlobExamSprintReportStorage.java`
  - Implement the updated domain storage port using domain `ReportType`; keep metadata value `reportType.name()`.
- `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.java`
  - Use domain `ReportType.OUTLOOK` in `supports(...)`; keep contract payload DTO usage unchanged.
- `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRenderer.java`
  - Use domain `ReportType.ACHIEVEMENT` in `supports(...)`; keep contract payload DTO usage unchanged.
- `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/storage/AzureBlobExamSprintReportStorageTest.java`
  - Update storage upload call to domain `ReportType` while preserving metadata assertions.
- `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.java`
  - Update renderer support assertions to domain `ReportType`.
- `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRendererTest.java`
  - Update renderer support assertions to domain `ReportType`.
- `ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/architecture/ExamSprintArchitectureTest.java`
  - Remove the domain-to-contract allowlist and make `domain -> contracts` a hard rule; keep Jackson allowlist unchanged.
- `docs/superpowers/specs/2026-04-27-ddd-naming-governance-design.md`
  - Record that `ReportType` migration cleared `domain -> contracts`; identify Jackson / payload governance as the next loop.

### Keep unchanged

- `abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/ExamSprintReportType.java`
  - Must remain public enum `ExamSprintReportType { OUTLOOK, ACHIEVEMENT }`.
- `abilities/exam-sprint/contracts/src/main/java/cn/yunzhixue/ability/center/examsprint/contracts/report/*Response.java`
  - Response DTO report type fields stay `ExamSprintReportType`.
- `abilities/exam-sprint/infrastructure/pom.xml`
  - Keep `exam-sprint-contracts` because renderers still use contract payload DTOs; payload migration is out of scope.
- `ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/examsprint/adapter/http/ExamSprintReportControllerWebMvcTest.java`
  - Should still use contract `ExamSprintReportType` and verify public JSON strings.

## Task 1: Establish current baseline and identify remaining debt

**Files:**
- Verify: `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/*.java`
- Verify: `abilities/exam-sprint/domain/pom.xml`
- Verify: `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/*.java`
- Verify: `ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/architecture/ExamSprintArchitectureTest.java`

- [ ] **Step 1: Confirm the branch and clean worktree**

Run:

```bash
git status -sb
git status --short
```

Expected: branch is `refactor/ddd命名治理二轮-report-type`; before implementation edits, `git status --short` should show only this new plan document as untracked.

- [ ] **Step 2: Search current domain-to-contract report references**

Run:

```bash
rg "contracts\.report" "abilities/exam-sprint/domain/src/main/java"
```

Expected current debt before migration:

```text
abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReport.java:import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReportRenderer.java:import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReportStorage.java:import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
```

- [ ] **Step 3: Search all current `ExamSprintReportType` usages**

Run:

```bash
rg "ExamSprintReportType" "abilities/exam-sprint"
```

Expected: matches in domain, application, infrastructure, contracts, and tests. Domain matches should be limited to `ExamSprintReport`, `ExamSprintReportRenderer`, and `ExamSprintReportStorage`.

- [ ] **Step 4: Run domain baseline tests**

Run:

```bash
mvn -q -pl abilities/exam-sprint/domain -am test
```

Expected: PASS. If it fails before editing, stop and record the exact failing module/test so the migration is not blamed for a pre-existing failure.

- [ ] **Step 5: Run application baseline tests with `-am`**

Run:

```bash
mvn -q -pl abilities/exam-sprint/application -am test
```

Expected: PASS. If it fails before editing, stop and record the exact failing module/test.

- [ ] **Checkpoint**

Record in the session summary: current remaining `domain -> contracts` debt is `ExamSprintReportType` only, and the initial full-worktree `mvn -q test` baseline already completed without a tool-reported failure.

## Task 2: Add domain-owned `ReportType` and failing mapper tests

**Files:**
- Create: `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ReportType.java`
- Modify: `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportContractMapperTest.java`
- Modify: `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportContractMapper.java`

- [ ] **Step 1: Add failing report type mapper tests first**

Modify `ExamSprintReportContractMapperTest.java` so it imports both the domain and contract report type concepts and contains these tests in addition to the existing status tests:

```java
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

```java
    @Test
    void mapsEveryDomainReportTypeToContractReportTypeWithTheSamePublicName() {
        for (ReportType domainReportType : ReportType.values()) {
            assertThat(ExamSprintReportContractMapper.toContractReportType(domainReportType).name())
                    .isEqualTo(domainReportType.name());
        }
    }

    @Test
    void mapsEveryContractReportTypeToDomainReportTypeWithTheSamePublicName() {
        for (ExamSprintReportType contractReportType : ExamSprintReportType.values()) {
            assertThat(ExamSprintReportContractMapper.toDomainReportType(contractReportType).name())
                    .isEqualTo(contractReportType.name());
        }
    }

    @Test
    void mapsNullReportTypeToNullInBothDirections() {
        assertThat(ExamSprintReportContractMapper.toContractReportType(null)).isNull();
        assertThat(ExamSprintReportContractMapper.toDomainReportType(null)).isNull();
    }
```

- [ ] **Step 2: Run mapper test and verify it fails before implementation**

Run:

```bash
mvn -q -pl abilities/exam-sprint/application -am -Dtest=ExamSprintReportContractMapperTest test
```

Expected: FAIL because `ReportType`, `toContractReportType(...)`, or `toDomainReportType(...)` is not implemented yet.

- [ ] **Step 3: Create domain enum with public-compatible literals**

Create `ReportType.java`:

```java
package cn.yunzhixue.ability.center.examsprint.domain.report;

public enum ReportType {
    OUTLOOK,
    ACHIEVEMENT
}
```

- [ ] **Step 4: Implement explicit mapper methods**

Modify `ExamSprintReportContractMapper.java` imports:

```java
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportGenerationStatus;
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportGenerationStatus;
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

Add these package-private methods after `toContractStatus(...)`:

```java
    static ExamSprintReportType toContractReportType(ReportType reportType) {
        return reportType == null ? null : ExamSprintReportType.valueOf(reportType.name());
    }

    static ReportType toDomainReportType(ExamSprintReportType reportType) {
        return reportType == null ? null : ReportType.valueOf(reportType.name());
    }
```

- [ ] **Step 5: Run mapper test again**

Run:

```bash
mvn -q -pl abilities/exam-sprint/application -am -Dtest=ExamSprintReportContractMapperTest test
```

Expected: PASS.

- [ ] **Commit checkpoint, only if explicitly requested**

Suggested message:

```text
test(exam-sprint): 覆盖报告类型边界映射
```

## Task 3: Migrate domain model and domain ports to domain `ReportType`

**Files:**
- Modify: `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReport.java`
- Modify: `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReportRenderer.java`
- Modify: `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReportStorage.java`

- [ ] **Step 1: Run domain tests before editing this task**

Run:

```bash
mvn -q -pl abilities/exam-sprint/domain -am test
```

Expected: PASS from the previous task’s state.

- [ ] **Step 2: Update `ExamSprintReport` type ownership**

In `ExamSprintReport.java`, remove:

```java
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
```

Change the record field and factory parameter:

```java
public record ExamSprintReport(
        String reportId,
        ReportType reportType,
        JsonNode payload,
        ReportGenerationStatus generationStatus,
        Instant createdAt,
        Instant updatedAt,
        Instant expiresAt,
        String storageObjectKey,
        String fileName,
        String failureReason) {
```

```java
    public static ExamSprintReport pending(
            String reportId,
            ReportType reportType,
            JsonNode payload,
            Instant createdAt,
            Instant expiresAt) {
```

- [ ] **Step 3: Update renderer domain port signature**

In `ExamSprintReportRenderer.java`, remove the contract enum import and use the same-package domain enum:

```java
public interface ExamSprintReportRenderer {

    boolean supports(ReportType reportType);

    String render(JsonNode payload, Instant generatedAt);
}
```

- [ ] **Step 4: Update storage domain port signature**

In `ExamSprintReportStorage.java`, remove the contract enum import and change the `upload(...)` parameter:

```java
    StoredExamSprintReportFile upload(
            String reportId,
            ReportType reportType,
            String fileName,
            byte[] pdfBytes,
            Instant expiresAt);
```

- [ ] **Step 5: Run domain tests and source grep**

Run:

```bash
mvn -q -pl abilities/exam-sprint/domain -am test
rg "contracts\.report" "abilities/exam-sprint/domain/src/main/java"
rg "ExamSprintReportType" "abilities/exam-sprint/domain/src/main/java"
```

Expected: domain tests PASS; both `rg` commands return no matches in domain main source.

- [ ] **Commit checkpoint, only if explicitly requested**

Suggested message:

```text
refactor(exam-sprint): 迁移领域报告类型
```

## Task 4: Update application boundary and application tests

**Files:**
- Modify: `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/DefaultExamSprintReportApplicationService.java`
- Modify: `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportApplicationServiceTest.java`
- Modify: `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportGenerationWorkerTest.java`

- [ ] **Step 1: Run application tests to expose compile failures after domain port changes**

Run:

```bash
mvn -q -pl abilities/exam-sprint/application -am test
```

Expected: FAIL until application code and tests stop passing contract `ExamSprintReportType` into domain APIs. Record the first compile/test failure as the expected red state.

- [ ] **Step 2: Change application service to use domain `ReportType` internally**

In `DefaultExamSprintReportApplicationService.java`, replace the report type import:

```java
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

Change entry methods and private method signatures:

```java
    public CreateExamSprintReportResponse createOutlookReport(JsonNode payload) {
        validateOutlookPayload(payload);
        return submitReportGeneration(ReportType.OUTLOOK, payload);
    }

    public CreateExamSprintReportResponse createAchievementReport(JsonNode payload) {
        validateAchievementPayload(payload);
        return submitReportGeneration(ReportType.ACHIEVEMENT, payload);
    }

    public CreateExamSprintReportWithUrlResponse createOutlookReportSync(JsonNode payload) {
        validateOutlookPayload(payload);
        return submitReportGenerationSync(ReportType.OUTLOOK, payload);
    }

    public CreateExamSprintReportWithUrlResponse createAchievementReportSync(JsonNode payload) {
        validateAchievementPayload(payload);
        return submitReportGenerationSync(ReportType.ACHIEVEMENT, payload);
    }

    private CreateExamSprintReportResponse submitReportGeneration(ReportType reportType, JsonNode payload) {
```

```java
    private CreateExamSprintReportWithUrlResponse submitReportGenerationSync(
            ReportType reportType,
            JsonNode payload) {
```

- [ ] **Step 3: Map domain report type when constructing response DTOs**

In `DefaultExamSprintReportApplicationService.java`, replace every response constructor argument that currently passes `report.reportType()` or `generatedReport.reportType()` directly.

For `CreateExamSprintReportResponse`:

```java
        return new CreateExamSprintReportResponse(
                report.reportId(),
                ExamSprintReportContractMapper.toContractReportType(report.reportType()),
                ExamSprintReportContractMapper.toContractStatus(report.generationStatus()),
                report.createdAt(),
                report.expiresAt());
```

For `CreateExamSprintReportWithUrlResponse`:

```java
        return new CreateExamSprintReportWithUrlResponse(
                generatedReport.reportId(),
                ExamSprintReportContractMapper.toContractReportType(generatedReport.reportType()),
                ExamSprintReportContractMapper.toContractStatus(generatedReport.generationStatus()),
                generatedReport.createdAt(),
                generatedReport.updatedAt(),
                generatedReport.expiresAt(),
                downloadUrl);
```

For `ExamSprintReportDetailResponse`:

```java
        return new ExamSprintReportDetailResponse(
                report.reportId(),
                ExamSprintReportContractMapper.toContractReportType(report.reportType()),
                ExamSprintReportContractMapper.toContractStatus(report.generationStatus()),
                report.createdAt(),
                report.updatedAt(),
                report.expiresAt(),
                downloadUrl,
                report.failureReason());
```

- [ ] **Step 4: Update application service tests by assertion target**

In `ExamSprintReportApplicationServiceTest.java`, use this rule:

```java
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

Domain entity assertions:

```java
assertThat(saved.reportType()).isEqualTo(ReportType.OUTLOOK);
assertThat(saved.reportType()).isEqualTo(ReportType.ACHIEVEMENT);
```

Contract response assertions remain on the public enum, using either the existing import or a fully qualified name:

```java
assertThat(response.reportType())
        .isEqualTo(cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType.OUTLOOK);
```

Test data that constructs `ExamSprintReport.pending(...)` or `new ExamSprintReport(...)` must use `ReportType.OUTLOOK` or `ReportType.ACHIEVEMENT`.

Test doubles implementing `ExamSprintReportRenderer` or `ExamSprintReportStorage` must update method parameters to domain `ReportType`:

```java
public boolean supports(ReportType reportType) {
    return reportType == ReportType.OUTLOOK || reportType == ReportType.ACHIEVEMENT;
}
```

```java
public StoredExamSprintReportFile upload(
        String reportId,
        ReportType reportType,
        String fileName,
        byte[] pdfBytes,
        Instant expiresAt) {
```

- [ ] **Step 5: Update generation worker tests**

In `ExamSprintReportGenerationWorkerTest.java`, replace contract enum use in domain construction and test doubles with:

```java
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

Examples:

```java
ExamSprintReport.pending("report-success", ReportType.OUTLOOK, payload, NOW, EXPIRES_AT)
```

```java
private final ReportType supportedReportType;

TestRenderer(ReportType supportedReportType) {
    this.supportedReportType = supportedReportType;
}

public boolean supports(ReportType reportType) {
    return reportType == supportedReportType;
}
```

- [ ] **Step 6: Run application verification**

Run:

```bash
mvn -q -pl abilities/exam-sprint/application -am test
```

Expected: PASS. Tests should clearly distinguish domain `ReportType` assertions from contract `ExamSprintReportType` response assertions.

- [ ] **Commit checkpoint, only if explicitly requested**

Suggested message:

```text
refactor(exam-sprint): 在应用边界映射报告类型
```

## Task 5: Update infrastructure adapters and tests

**Files:**
- Modify: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/storage/InMemoryExamSprintReportStorage.java`
- Modify: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/storage/AzureBlobExamSprintReportStorage.java`
- Modify: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRenderer.java`
- Modify: `abilities/exam-sprint/infrastructure/src/main/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRenderer.java`
- Modify: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/storage/AzureBlobExamSprintReportStorageTest.java`
- Modify: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/outlook/ClasspathOutlookExamSprintReportRendererTest.java`
- Modify: `abilities/exam-sprint/infrastructure/src/test/java/cn/yunzhixue/ability/center/examsprint/infrastructure/report/rendering/achievement/ClasspathAchievementExamSprintReportRendererTest.java`

- [ ] **Step 1: Run infrastructure tests to expose compile failures after domain port changes**

Run:

```bash
mvn -q -pl abilities/exam-sprint/infrastructure -am test
```

Expected: FAIL until infrastructure implementations use domain `ReportType` in overridden port methods. Record the first compile/test failure as the expected red state.

- [ ] **Step 2: Update storage implementations**

In `InMemoryExamSprintReportStorage.java` and `AzureBlobExamSprintReportStorage.java`, replace the contract enum import with:

```java
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

Change the `upload(...)` signature in both classes:

```java
    public StoredExamSprintReportFile upload(
            String reportId,
            ReportType reportType,
            String fileName,
            byte[] pdfBytes,
            Instant expiresAt) {
```

Keep Azure metadata unchanged in behavior:

```java
"reportType", reportType.name(),
```

- [ ] **Step 3: Update renderer implementations without changing payload DTOs**

In `ClasspathOutlookExamSprintReportRenderer.java`, keep `OutlookExamSprintReportPayload` and replace only the enum import/support logic:

```java
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

```java
    public boolean supports(ReportType reportType) {
        return reportType == ReportType.OUTLOOK;
    }
```

In `ClasspathAchievementExamSprintReportRenderer.java`, keep `AchievementExamSprintReportPayload` and replace only the enum import/support logic:

```java
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

```java
    public boolean supports(ReportType reportType) {
        return reportType == ReportType.ACHIEVEMENT;
    }
```

- [ ] **Step 4: Update infrastructure tests**

Replace contract enum imports in these tests with domain `ReportType`:

```java
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
```

Update assertions/calls:

```java
assertThat(renderer.supports(ReportType.OUTLOOK)).isTrue();
assertThat(renderer.supports(ReportType.ACHIEVEMENT)).isFalse();
```

```java
assertThat(renderer.supports(ReportType.ACHIEVEMENT)).isTrue();
assertThat(renderer.supports(ReportType.OUTLOOK)).isFalse();
```

```java
storage.upload(
        "report-1",
        ReportType.OUTLOOK,
        "exam-sprint-outlook-report-report-1.pdf",
        pdfBytes,
        expiresAt);
```

Keep any metadata assertions expecting the public string value:

```java
assertThat(metadata).containsEntry("reportType", "OUTLOOK");
```

- [ ] **Step 5: Run infrastructure verification**

Run:

```bash
mvn -q -pl abilities/exam-sprint/infrastructure -am test
```

Expected: PASS. Do not remove `exam-sprint-contracts` from `abilities/exam-sprint/infrastructure/pom.xml` because contract payload DTO usage remains in scope for infrastructure renderers.

- [ ] **Commit checkpoint, only if explicitly requested**

Suggested message:

```text
refactor(exam-sprint): 适配基础设施报告类型
```

## Task 6: Remove domain contracts dependency and tighten architecture guardrail

**Files:**
- Modify: `abilities/exam-sprint/domain/pom.xml`
- Modify: `ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/architecture/ExamSprintArchitectureTest.java`

- [ ] **Step 1: Remove `exam-sprint-contracts` from domain POM**

In `abilities/exam-sprint/domain/pom.xml`, delete only this dependency block:

```xml
<dependency>
    <groupId>cn.yunzhixue</groupId>
    <artifactId>exam-sprint-contracts</artifactId>
    <version>${project.version}</version>
</dependency>
```

Keep:

```xml
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
```

- [ ] **Step 2: Verify domain compiles without contracts dependency**

Run:

```bash
mvn -q -pl abilities/exam-sprint/domain -am test
rg "exam-sprint-contracts" "abilities/exam-sprint/domain/pom.xml"
rg "contracts\.report" "abilities/exam-sprint/domain/src/main/java"
```

Expected: domain tests PASS; both `rg` commands return no matches for domain contracts dependency/source imports.

- [ ] **Step 3: Tighten ArchUnit domain-to-contract rule**

In `ExamSprintArchitectureTest.java`, remove this field:

```java
private static final Set<String> CURRENT_DOMAIN_CONTRACT_DEBT = Set.of(
        "ExamSprintReport",
        "ExamSprintReportRenderer",
        "ExamSprintReportStorage");
```

Replace the current rule:

```java
@ArchTest
static final ArchRule new_domain_classes_should_not_depend_on_contracts = noClasses()
        .that().resideInAPackage("..domain..")
        .and(areNotNamed(CURRENT_DOMAIN_CONTRACT_DEBT))
        .should().dependOnClassesThat().resideInAPackage("..contracts..");
```

with the hard rule:

```java
@ArchTest
static final ArchRule domain_should_not_depend_on_contracts = noClasses()
        .that().resideInAPackage("..domain..")
        .should().dependOnClassesThat().resideInAPackage("..contracts..");
```

Keep `CURRENT_DOMAIN_JACKSON_DEBT` and `areNotNamed(...)` because the Jackson allowlist remains intentionally out of scope.

- [ ] **Step 4: Run architecture and runtime verification**

Run:

```bash
mvn -q -pl ability-center-runtime -Dtest=ExamSprintArchitectureTest test
mvn -q -pl ability-center-runtime -am test
```

Expected: PASS. Runtime controller tests should continue to verify public response JSON still uses `"OUTLOOK"` / `"ACHIEVEMENT"` strings via contract response DTOs.

- [ ] **Commit checkpoint, only if explicitly requested**

Suggested message:

```text
test(exam-sprint): 收紧领域依赖架构守护
```

## Task 7: Update governance documentation

**Files:**
- Modify: `docs/superpowers/specs/2026-04-27-ddd-naming-governance-design.md`
- Verify: `docs/superpowers/plans/2026-04-28-ddd-naming-governance-report-type-loop.md`

- [ ] **Step 1: Update design document status line**

Change the status near the top from “remaining `domain -> contracts` debt is `ReportType`” to:

```markdown
> Status: Second governance loop implemented for `ReportType`; `exam-sprint-domain` no longer depends on `exam-sprint-contracts`. Remaining near-term debt is Jackson / `JsonNode` payload governance.
```

- [ ] **Step 2: Update the technical debt register**

Replace the `domain -> contracts` row with an implementation result row or remove it from active debts. Use this wording to preserve history without listing it as active debt:

```markdown
| `domain -> contracts` | Cleared in the `ReportType` loop after `ReportGenerationStatus` and `ReportType` became domain-owned | keep ArchUnit hard rule; map future contract/domain duplicates in application |
```

Keep the `domain -> jackson`, payload records, mixed model semantics, `Storage`/`Renderer`/`PdfGenerator`, and application orchestration rows unchanged except for any wording needed to identify them as next-loop candidates.

- [ ] **Step 3: Update first-loop / next-loop summary**

Replace the existing next-loop sentence:

```markdown
Next recommended loop: migrate `ReportType` into domain, remove `exam-sprint-domain` dependency on `exam-sprint-contracts`, and tighten the architecture allowlist accordingly.
```

with:

```markdown
Second loop result: `ReportType` is now domain-owned; application maps it to and from the public `ExamSprintReportType`; `exam-sprint-domain` no longer depends on `exam-sprint-contracts`; the domain-to-contract architecture rule is now a hard guardrail.

Next recommended loop: address Jackson / `JsonNode` payload leakage by introducing strongly named domain report-content concepts for one report type while keeping external payload DTOs stable.
```

- [ ] **Step 4: Run documentation grep checks**

Run:

```bash
rg "ReportType|domain -> contracts|JsonNode" "docs/superpowers/specs/2026-04-27-ddd-naming-governance-design.md" "docs/superpowers/plans/2026-04-28-ddd-naming-governance-report-type-loop.md"
```

Expected: matches describe `ReportType` as implemented after this loop and `JsonNode` as next-loop debt; no text should say `ReportType` is still active remaining `domain -> contracts` debt.

- [ ] **Commit checkpoint, only if explicitly requested**

Suggested message:

```text
docs(exam-sprint): 记录DDD命名治理二轮计划与结果
```

## Task 8: Final full verification and review preparation

**Files:**
- Verify: all modified files

- [ ] **Step 1: Verify no domain main source depends on contracts report package**

Run:

```bash
rg "contracts\.report" "abilities/exam-sprint/domain/src/main/java"
```

Expected: no matches.

- [ ] **Step 2: Verify domain no longer uses the public API report type enum**

Run:

```bash
rg "ExamSprintReportType" "abilities/exam-sprint/domain/src/main/java"
```

Expected: no matches.

- [ ] **Step 3: Verify domain POM no longer depends on contracts**

Run:

```bash
rg "exam-sprint-contracts" "abilities/exam-sprint/domain/pom.xml"
```

Expected: no matches.

- [ ] **Step 4: Run targeted module suites with `-am`**

Run:

```bash
mvn -q -pl abilities/exam-sprint/application -am test
mvn -q -pl abilities/exam-sprint/infrastructure -am test
mvn -q -pl ability-center-runtime -am test
```

Expected: all PASS.

- [ ] **Step 5: Run full reactor tests**

Run:

```bash
mvn -q test
```

Expected: PASS. If unrelated failures occur, record exact module and test names before deciding whether to fix or defer.

- [ ] **Step 6: Check git status**

Run:

```bash
git status --short
```

Expected: only files modified by this plan are listed.

- [ ] **Step 7: Prepare code review checklist**

Review these points before claiming completion:

```text
- domain main source has no `contracts.report` imports and no `ExamSprintReportType` usage.
- `abilities/exam-sprint/domain/pom.xml` has no `exam-sprint-contracts` dependency.
- contracts `ExamSprintReportType` remains named the same and still exposes `OUTLOOK`, `ACHIEVEMENT`.
- application mapper covers all current enum values in both directions and null strategy.
- response DTOs still use contracts `ExamSprintReportType`.
- runtime/controller JSON shape remains unchanged.
- ArchUnit domain-to-contract allowlist is removed or reduced to a hard rule.
- Jackson / `JsonNode` allowlist remains intentionally unchanged.
- no Storage/Renderer/PdfGenerator move, no payload DTO migration, no application service split.
```

- [ ] **Commit checkpoint, only if explicitly requested**

Suggested message:

```text
refactor(exam-sprint): 完成DDD命名治理二轮类型迁移
```

## Implementation Handoff

Recommended execution mode:

1. Use subagent-driven development, with one task per implementation subagent after Task 1/2 establishes the red-green mapping baseline.
2. Review after each task for dependency direction, API compatibility, and scope control.
3. Run `mvn -q test`, `git status --short`, and `rg "contracts\.report" "abilities/exam-sprint/domain/src/main/java"` before claiming the loop is complete.

Do not proceed to a Jackson / payload governance loop until:

- full tests have passed for this loop;
- the architecture rule has been tightened;
- the external response enum literals remain unchanged;
- this plan and the long-term governance document record the `ReportType` loop outcome.
