# 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 ``: ```xml com.tngtech.archunit archunit-junit5 1.3.0 test ``` - [ ] **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 CURRENT_DOMAIN_CONTRACT_DEBT = Set.of( "ExamSprintReport", "ExamSprintReportRenderer", "ExamSprintReportStorage"); private static final Set 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 areNotNamed(Set 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 cn.yunzhixue exam-sprint-contracts ${project.version} ``` 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.