|
|
@@ -0,0 +1,906 @@
|
|
|
+# 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.
|