# DDD Naming Governance First 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:** Establish the first DDD governance loop by moving report generation lifecycle status into `domain`, mapping it back to public contract responses, and adding an architecture-test baseline that prevents new domain dependency violations.

**Architecture:** Keep the public API stable while shifting ownership of lifecycle language from `contracts` to `domain`. `domain` owns `ReportGenerationStatus`; `application` maps domain status to `contracts.ExamSprintReportGenerationStatus`; `runtime` continues returning the same JSON values. Architecture tests live in `ability-center-runtime` because that module sees the complete assembled classpath.

**Tech Stack:** Java 17, Maven multi-module build, Spring Boot 3.3.5, JUnit 5, ArchUnit, existing Spring Boot test setup.

---

> **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 after first-loop implementation:** Task 2/3 verification was handled as a continuous loop because application `testCompile` ordering and local artifact issues could leave stale module artifacts when commands were run without `-am`. A runtime test also had a stale domain status assertion and was updated minimally. In submodule verification scenarios, prefer commands with `-am` to rebuild required modules and avoid stale artifacts.

> **Execution status:** First loop implemented in this worktree. The checklist remains the original plan for traceability and is intentionally left unchecked. Final verification after the Task 7 docs update: `mvn -q test` passed.

## Target File Structure

### Create

- `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ReportGenerationStatus.java`
  - Domain-owned lifecycle status for report generation.
- `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportContractMapper.java`
  - Package-private application mapper from domain concepts to public contract types.
- `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportContractMapperTest.java`
  - Verifies every domain status maps to the existing public contract enum value.
- `ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/architecture/ExamSprintArchitectureTest.java`
  - Baseline architecture tests with current debt allowlisted.

### Modify

- `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReport.java`
  - Use domain `ReportGenerationStatus` instead of contract `ExamSprintReportGenerationStatus`.
- `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/DefaultExamSprintReportApplicationService.java`
  - Compare against domain `ReportGenerationStatus` and map response status back to contract status.
- `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportGenerationPipeline.java`
  - Compare against domain `ReportGenerationStatus`.
- `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportApplicationServiceTest.java`
  - Update domain status assertions where tests inspect `ExamSprintReport`; keep response assertions on contract status.
- `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportGenerationWorkerTest.java`
  - Update domain status assertions.
- `ability-center-runtime/pom.xml`
  - Add ArchUnit test dependency.

## Task 1: Add domain-owned generation status

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

- [ ] **Step 1: Run the current domain tests as a baseline**

Run:

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

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

- [ ] **Step 2: Create `ReportGenerationStatus` in domain**

Create `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ReportGenerationStatus.java`:

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

public enum ReportGenerationStatus {
    PENDING,
    PROCESSING,
    SUCCESS,
    FAILED,
    EXPIRED
}
```

- [ ] **Step 3: Update `ExamSprintReport` to use the domain status**

In `ExamSprintReport.java`, remove this import:

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

Change the record field from:

```java
ExamSprintReportGenerationStatus generationStatus,
```

to:

```java
ReportGenerationStatus generationStatus,
```

Change all status references in the same file:

```java
ExamSprintReportGenerationStatus.PENDING
ExamSprintReportGenerationStatus.PROCESSING
ExamSprintReportGenerationStatus.SUCCESS
ExamSprintReportGenerationStatus.FAILED
ExamSprintReportGenerationStatus.EXPIRED
```

to:

```java
ReportGenerationStatus.PENDING
ReportGenerationStatus.PROCESSING
ReportGenerationStatus.SUCCESS
ReportGenerationStatus.FAILED
ReportGenerationStatus.EXPIRED
```

- [ ] **Step 4: Run domain tests to verify the domain module compiles**

Run:

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

Expected: domain module compiles. Remaining domain-to-contract dependency still exists through `ExamSprintReportType`, `ExamSprintReportRenderer`, and `ExamSprintReportStorage`; this task only migrates generation status.

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

Suggested message:

```text
refactor: move report generation status into domain
```

## Task 2: Add application contract mapping

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

- [ ] **Step 1: Write the mapper test first**

Create `abilities/exam-sprint/application/src/test/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportContractMapperTest.java`:

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

import cn.yunzhixue.ability.center.examsprint.domain.report.ReportGenerationStatus;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class ExamSprintReportContractMapperTest {

    @Test
    void mapsEveryDomainGenerationStatusToContractStatusWithTheSamePublicName() {
        for (ReportGenerationStatus domainStatus : ReportGenerationStatus.values()) {
            assertThat(ExamSprintReportContractMapper.toContractStatus(domainStatus).name())
                    .isEqualTo(domainStatus.name());
        }
    }

    @Test
    void mapsNullGenerationStatusToNull() {
        assertThat(ExamSprintReportContractMapper.toContractStatus(null)).isNull();
    }
}
```

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

Run:

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

Expected: FAIL because `ExamSprintReportContractMapper` does not exist.

- [ ] **Step 3: Implement the mapper**

Create `abilities/exam-sprint/application/src/main/java/cn/yunzhixue/ability/center/examsprint/application/report/ExamSprintReportContractMapper.java`:

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

import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportGenerationStatus;
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportGenerationStatus;

final class ExamSprintReportContractMapper {

    private ExamSprintReportContractMapper() {
    }

    static ExamSprintReportGenerationStatus toContractStatus(ReportGenerationStatus status) {
        return status == null ? null : ExamSprintReportGenerationStatus.valueOf(status.name());
    }
}
```

- [ ] **Step 4: Run the mapper test again**

Run:

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

Expected: PASS.

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

Suggested message:

```text
refactor: map domain generation status to contracts
```

## Task 3: Update application code to use domain status internally

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

- [ ] **Step 1: Run application tests before editing**

Run:

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

Expected: FAIL after Task 1 if application still imports contract `ExamSprintReportGenerationStatus` for domain status comparisons.

- [ ] **Step 2: Update imports in `DefaultExamSprintReportApplicationService`**

Replace:

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

with:

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

- [ ] **Step 3: Update status comparisons in `DefaultExamSprintReportApplicationService`**

Replace comparisons such as:

```java
generatedReport.generationStatus() != ExamSprintReportGenerationStatus.SUCCESS
report.generationStatus() != ExamSprintReportGenerationStatus.EXPIRED
report.generationStatus() == ExamSprintReportGenerationStatus.SUCCESS
```

with:

```java
generatedReport.generationStatus() != ReportGenerationStatus.SUCCESS
report.generationStatus() != ReportGenerationStatus.EXPIRED
report.generationStatus() == ReportGenerationStatus.SUCCESS
```

- [ ] **Step 4: Map status when constructing contract responses**

In `DefaultExamSprintReportApplicationService`, every `new CreateExamSprintReportResponse`, `new CreateExamSprintReportWithUrlResponse`, and `new ExamSprintReportDetailResponse` call must pass:

```java
ExamSprintReportContractMapper.toContractStatus(report.generationStatus())
```

or the equivalent variable-specific expression instead of passing `report.generationStatus()` directly.

For example, change:

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

to:

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

- [ ] **Step 5: Update imports and comparisons in `ExamSprintReportGenerationPipeline`**

Replace:

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

with:

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

Replace:

```java
report.generationStatus() != ExamSprintReportGenerationStatus.PENDING
```

with:

```java
report.generationStatus() != ReportGenerationStatus.PENDING
```

- [ ] **Step 6: Compile the application module**

Run:

```powershell
mvn -q -pl abilities/exam-sprint/application -am -DskipTests compile
```

Expected: PASS. Full application tests run in Task 4 after test imports distinguish domain status from contract response status.

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

Suggested message:

```text
refactor: use domain generation status in application flow
```

## Task 4: Update tests for domain-vs-contract status ownership

**Files:**
- 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: Locate contract status usages in application tests**

Run:

```powershell
rg "ExamSprintReportGenerationStatus" "abilities/exam-sprint/application/src/test/java"
```

Expected: matches in application tests that currently import the contract enum.

- [ ] **Step 2: Split status imports by assertion target**

Use this rule:

- assertions against `ExamSprintReport.generationStatus()` use `cn.yunzhixue.ability.center.examsprint.domain.report.ReportGenerationStatus`;
- assertions against `CreateExamSprintReportResponse`, `CreateExamSprintReportWithUrlResponse`, or `ExamSprintReportDetailResponse` use `cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportGenerationStatus`.

When both are needed in the same test file, import the domain enum and refer to the contract enum with a fully qualified name to avoid ambiguous names.

Example domain assertion:

```java
assertThat(savedReport.generationStatus()).isEqualTo(ReportGenerationStatus.SUCCESS);
```

Example contract response assertion:

```java
assertThat(response.generationStatus())
        .isEqualTo(cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportGenerationStatus.SUCCESS);
```

- [ ] **Step 3: Run application tests**

Run:

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

Expected: PASS for application module tests.

- [ ] **Step 4: Run runtime tests to verify response compatibility**

Run:

```powershell
mvn -q -pl ability-center-runtime -am test
```

Expected: runtime tests pass and API responses still expose the original public enum names: `PENDING`, `PROCESSING`, `SUCCESS`, `FAILED`, `EXPIRED`.

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

Suggested message:

```text
test: distinguish domain and contract generation status
```

## Task 5: Add architecture baseline tests

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

- [ ] **Step 1: Add ArchUnit dependency to runtime test scope**

In `ability-center-runtime/pom.xml`, add this dependency inside `<dependencies>`:

```xml
<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>1.3.0</version>
    <scope>test</scope>
</dependency>
```

- [ ] **Step 2: Write architecture tests with current domain debt allowlisted**

Create `ability-center-runtime/src/test/java/cn/yunzhixue/ability/center/architecture/ExamSprintArchitectureTest.java`:

```java
package cn.yunzhixue.ability.center.architecture;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.base.DescribedPredicate;

import java.util.Set;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

@AnalyzeClasses(
        packages = "cn.yunzhixue.ability.center.examsprint",
        importOptions = ImportOption.DoNotIncludeTests.class)
class ExamSprintArchitectureTest {

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

    private static final Set<String> CURRENT_DOMAIN_JACKSON_DEBT = Set.of(
            "ExamSprintReport",
            "ExamSprintReportRenderer");

    @ArchTest
    static final ArchRule contracts_should_not_depend_on_inner_layers = noClasses()
            .that().resideInAPackage("..contracts..")
            .should().dependOnClassesThat().resideInAnyPackage(
                    "..domain..",
                    "..application..",
                    "..infrastructure..",
                    "..adapter.."
            );

    @ArchTest
    static final ArchRule infrastructure_should_not_depend_on_runtime_adapters = noClasses()
            .that().resideInAPackage("..infrastructure..")
            .should().dependOnClassesThat().resideInAnyPackage(
                    "..adapter..",
                    "..configuration.."
            );

    @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..");

    @ArchTest
    static final ArchRule new_domain_classes_should_not_depend_on_jackson = noClasses()
            .that().resideInAPackage("..domain..")
            .and(areNotNamed(CURRENT_DOMAIN_JACKSON_DEBT))
            .should().dependOnClassesThat().resideInAnyPackage(
                    "com.fasterxml.jackson.."
            );

    private static DescribedPredicate<JavaClass> areNotNamed(Set<String> simpleNames) {
        return new DescribedPredicate<>("are not named " + simpleNames) {
            @Override
            public boolean test(JavaClass input) {
                return !simpleNames.contains(input.getSimpleName());
            }
        };
    }
}
```

- [ ] **Step 3: Run architecture tests**

Run:

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

Expected: PASS. If this fails because additional historical debt exists, add only the exact existing class names to the relevant `CURRENT_DOMAIN_*_DEBT` set and document why in the PR description.

- [ ] **Step 4: Run all runtime tests**

Run:

```powershell
mvn -q -pl ability-center-runtime -am test
```

Expected: PASS.

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

Suggested message:

```text
test: add exam sprint architecture guardrails
```

## Task 6: Remove the migrated contract status dependency from domain imports

**Files:**
- Verify: `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/ExamSprintReport.java`
- Verify: `abilities/exam-sprint/domain/src/main/java/cn/yunzhixue/ability/center/examsprint/domain/report/*.java`
- Verify: `abilities/exam-sprint/domain/pom.xml`

- [ ] **Step 1: Search for remaining contract status imports in domain**

Run:

```powershell
rg "ExamSprintReportGenerationStatus" "abilities/exam-sprint/domain/src/main/java"
```

Expected: no matches.

- [ ] **Step 2: Search for remaining domain contract imports**

Run:

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

Expected: matches remain only for the current known debt around `ExamSprintReportType`, likely in:

```text
ExamSprintReport.java
ExamSprintReportRenderer.java
ExamSprintReportStorage.java
```

- [ ] **Step 3: Leave `domain/pom.xml` contracts dependency in place for this loop**

Do not remove this dependency yet:

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

Reason: `ExamSprintReportType` still lives in contracts. Removing the dependency belongs to the next governance loop that migrates `ReportType`.

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

Run:

```powershell
mvn -q test
```

Expected: PASS. If unrelated failures occur, record the exact failing module and test names before deciding whether to fix them in this loop or defer.

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

Suggested message:

```text
refactor: reduce domain dependency on report contracts
```

## Task 7: Update governance tracking after implementation

**Files:**
- Modify: `docs/superpowers/specs/2026-04-27-ddd-naming-governance-design.md`
- Modify: `docs/superpowers/plans/2026-04-27-ddd-naming-governance-first-loop.md` only if execution notes differ from this plan.

- [ ] **Step 1: Update the debt register result**

In `docs/superpowers/specs/2026-04-27-ddd-naming-governance-design.md`, update the debt register entry for `domain -> contracts` to mention that `ReportGenerationStatus` has been migrated and `ReportType` remains.

Use wording like:

```markdown
| `domain -> contracts` | `ReportType` still reuses API enum after `ReportGenerationStatus` migration | domain-owned `ReportType` plus application mapper |
```

- [ ] **Step 2: Add next-loop recommendation**

In the same design document, under “First Governance Loop”, add the next recommended loop:

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

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

Run:

```powershell
rg "ReportGenerationStatus" "docs/superpowers/specs/2026-04-27-ddd-naming-governance-design.md" "docs/superpowers/plans/2026-04-27-ddd-naming-governance-first-loop.md"
```

Expected: matches describe both the completed first loop and next governance direction clearly.

- [ ] **Step 4: Final verification**

Run:

```powershell
mvn -q test
```

Expected: PASS.

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

Suggested message:

```text
docs: record ddd governance first loop outcome
```

## Implementation Handoff

Recommended execution mode:

1. Use subagent-driven development, one task per subagent, because tasks are mostly independent after Task 1 establishes the enum.
2. Review after each task with emphasis on API compatibility and dependency direction.
3. Run `mvn -q test` before claiming the loop is complete.

Do not proceed to the second governance loop until:

- full tests have passed;
- architecture tests are passing;
- external response enum values remain unchanged;
- remaining `domain -> contracts` debt is documented as `ReportType` migration work.
