cisd/docs/plans/2026-03-10-init-execute-async-resume-plan.md
2026-03-11 14:23:11 +08:00

189 lines
7.9 KiB
Markdown

# Init Execute Async Resume Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Make `/api/v1/init/tasks/{taskId}/execute` return immediately after accepting work, then run init steps in the background and resume from the first non-`SUCCESS` step on retry.
**Architecture:** Keep the existing task table and step table as the source of truth. Split `InitService.executeTask` into an accept-and-dispatch path plus a background execution path, and use existing step statuses to determine where to resume. Add a lightweight in-process guard so one `taskId` cannot be started twice concurrently in the same application instance.
**Tech Stack:** Spring Boot service/controller layer, Java concurrency utilities, MyBatis-style repositories already present in the module, JUnit 5
---
### Task 1: Lock down current behavior with service tests
**Files:**
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java`
- Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java`
**Step 1: Write the failing tests**
Add tests that assert:
- `executeTask(taskId)` returns before a deliberately blocking executor finishes all steps
- retry skips steps already marked `SUCCESS` and resumes from the first `FAILED` step
- residual `RUNNING` steps are treated as the resume point on retry
- duplicate execute calls for the same running task do not launch a second execution
**Step 2: Run tests to verify they fail**
Run: `mvn -q -Dtest=InitServiceTest#shouldAcceptTaskExecutionAsynchronously,InitServiceTest#shouldResumeFromFirstFailedStepOnRetry,InitServiceTest#shouldResumeFromResidualRunningStep,InitServiceTest#shouldNotDispatchDuplicateBackgroundExecutionForSameTask test`
Expected: FAIL because execution is still synchronous and always starts from step 1.
**Step 3: Write minimal test scaffolding**
Extend the in-memory executor/repository helpers in the test file so they can:
- block on a latch or sleep
- count executed step codes
- fail a chosen step once and succeed on retry
Do not change production code in this step.
**Step 4: Run tests to verify RED is stable**
Run: `mvn -q -Dtest=InitServiceTest#shouldAcceptTaskExecutionAsynchronously,InitServiceTest#shouldResumeFromFirstFailedStepOnRetry,InitServiceTest#shouldResumeFromResidualRunningStep,InitServiceTest#shouldNotDispatchDuplicateBackgroundExecutionForSameTask test`
Expected: FAIL with assertion mismatches that prove the new behavior is not implemented yet.
**Step 5: Commit**
Skip commit in the current dirty workspace per user instruction.
### Task 2: Introduce async dispatch in `InitService`
**Files:**
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/init/service/InitService.java`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java`
**Step 1: Implement minimal async acceptance path**
Refactor `InitService` so `executeTask(taskId)`:
- loads task and steps
- returns a snapshot response quickly
- submits real work to a background executor
Use a small in-process dispatcher owned by `InitService`:
- `ExecutorService` or equivalent
- active-task map keyed by `taskId`
**Step 2: Keep the real execution logic in a dedicated method**
Create a private method such as `executeTaskInBackground(taskId)` that:
- reloads task and steps from repository
- updates statuses
- runs steps serially
- persists each step result immediately
- updates final task status
**Step 3: Run focused tests**
Run: `mvn -q -Dtest=InitServiceTest#shouldAcceptTaskExecutionAsynchronously,InitServiceTest#shouldNotDispatchDuplicateBackgroundExecutionForSameTask test`
Expected: PASS
**Step 4: Refactor for readability**
Extract helpers only if needed:
- build response snapshot
- mark task running
- remove active-task entry in `finally`
Do not add extra features.
**Step 5: Commit**
Skip commit in the current dirty workspace per user instruction.
### Task 3: Implement resume-from-first-non-success semantics
**Files:**
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/init/service/InitService.java`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java`
**Step 1: Implement step selection logic**
Before running steps in the background:
- convert residual `RUNNING` steps to `FAILED`
- skip every step already marked `SUCCESS`
- start execution at the first non-`SUCCESS` step
Preserve current semantics after the resume point:
- mark current step `RUNNING`
- on success mark `SUCCESS`
- on failure mark `FAILED` and stop
**Step 2: Preserve response counters**
Ensure `successSteps` in `InitTaskExecuteResponse` reflects the current number of successful steps, including successes from earlier runs.
**Step 3: Run focused tests**
Run: `mvn -q -Dtest=InitServiceTest#shouldResumeFromFirstFailedStepOnRetry,InitServiceTest#shouldResumeFromResidualRunningStep test`
Expected: PASS
**Step 4: Run the earlier async tests again**
Run: `mvn -q -Dtest=InitServiceTest#shouldAcceptTaskExecutionAsynchronously,InitServiceTest#shouldNotDispatchDuplicateBackgroundExecutionForSameTask,InitServiceTest#shouldResumeFromFirstFailedStepOnRetry,InitServiceTest#shouldResumeFromResidualRunningStep test`
Expected: PASS
**Step 5: Commit**
Skip commit in the current dirty workspace per user instruction.
### Task 4: Align controller and API-facing semantics
**Files:**
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/init/controller/InitInternalController.java`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/init/dto/InitTaskExecuteResponse.java`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java`
**Step 1: Make the API comments and DTO semantics match async behavior**
Update JavaDoc/comments so they describe:
- execute endpoint only accepts and starts background work
- polling should use existing detail/step/log APIs
If needed, add a field only if tests prove it is necessary. Prefer reusing the current DTO unchanged.
**Step 2: Run focused verification**
Run: `mvn -q -Dtest=InitServiceTest test`
Expected: PASS for all service tests, including the pre-existing create/query/execute cases.
**Step 3: Review the controller surface**
Check that:
- route path stays unchanged
- response wrapper stays unchanged
- no new endpoint is introduced unnecessarily
**Step 4: Commit**
Skip commit in the current dirty workspace per user instruction.
### Task 5: Final verification
**Files:**
- Verify only
**Step 1: Run init service tests**
Run: `mvn -q -Dtest=InitServiceTest test`
Expected: PASS
**Step 2: Run init executor regression tests touched by earlier behavior changes**
Run: `mvn -q -Dtest=ConfigurableInitStepExecutorTest#shouldInjectDatabaseCredentialsWhenExecutingStandardDbApplyCommand,ConfigurableInitStepExecutorTest#shouldExposeStandardInitScriptTemplates,ConfigurableInitStepExecutorTest#shouldStartStandardCmspCmtpWithConfiguredCommand test`
Expected: PASS
**Step 3: Run compile**
Run: `mvn -q -DskipTests compile`
Expected: PASS
**Step 4: Review focused diff**
Run: `git diff -- src/main/java/com/cisd/tms/modules/init/service/InitService.java src/main/java/com/cisd/tms/modules/init/controller/InitInternalController.java src/main/java/com/cisd/tms/modules/init/dto/InitTaskExecuteResponse.java src/test/java/com/cisd/tms/modules/init/service/InitServiceTest.java docs/plans/2026-03-10-init-execute-async-resume-design.md docs/plans/2026-03-10-init-execute-async-resume-plan.md`
Expected: only async-dispatch, resume logic, and related documentation changes.
**Step 5: Commit**
Skip commit in the current dirty workspace per user instruction.