From 2a329a931215b43bc0ce14ddccfbf2c8a54093c6 Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Thu, 23 Jul 2026 15:09:56 +0800 Subject: [PATCH] docs: plan WMS-native tidy recovery --- .../2026-07-23-wms-native-tidy-recovery.md | 1034 +++++++++++++++++ ...6-07-23-wms-native-tidy-recovery-design.md | 2 +- 2 files changed, 1035 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-07-23-wms-native-tidy-recovery.md diff --git a/docs/superpowers/plans/2026-07-23-wms-native-tidy-recovery.md b/docs/superpowers/plans/2026-07-23-wms-native-tidy-recovery.md new file mode 100644 index 0000000..cf85b10 --- /dev/null +++ b/docs/superpowers/plans/2026-07-23-wms-native-tidy-recovery.md @@ -0,0 +1,1034 @@ +# WMS-Native Tidy Recovery 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:** Replace WMS restart recovery's WCS `tidyTaskState` dependency with transactional decisions based only on WMS workflow, job, inventory, cargo-space, and lock evidence. + +**Architecture:** `WmsAlgorithmMapper` returns one immutable `TidyRecoveryEvidence` projection for each non-terminal tidy workflow. `TidyWorkflowRecoveryService` applies the approved decision table inside the WMS transaction manager and delegates a locally proven archived completion to `TidyWorkflowTransaction` with callback stage `RECOVERY_FINISHED`; no recovery path performs HTTP or repeats inventory movement. + +**Tech Stack:** Java 17 records, Spring Boot transactions, MyBatis XML, SQL Server, JUnit 5, AssertJ, Mockito, PowerShell 7/Pester, Node.js built-in HTTP simulator. + +## Global Constraints + +- A tidy move remains a normal WMS job with `in_or_out='28'`; normal completion continues through `alg.usp_complete_move_job` and `jobFinish`. +- WMS-local state is the only completion and restart-recovery authority; do not add or retain a WCS task-state API. +- Keep `tidy-wcs-snapshot-url` and `WcsVehicleSnapshotClient` unchanged because manual capacity still needs vehicle availability. +- Never call `alg.usp_complete_move_job` during recovery of an already archived job. +- A workflow in `PAUSED_RECOVERY` is never changed automatically and its active locks are never released automatically. +- Database/query exceptions propagate out of the transactional recovery method; they are not translated into recovery reasons. +- Preserve normal `MOVE_FINISHED` and `OUTBOUND_FINISHED` callback behavior and duplicate-callback idempotency. +- Do not change outbound-relocation recovery or unrelated WMS/WCS endpoints. +- The WMS repository has extensive user changes. Stage and commit only the exact files listed by each task; never reset, unstage, or overwrite unrelated work. +- `D:/workSpace/PROJECT_SPACE/FT546` has no usable root Git metadata. Replay and handoff files outside `FT546MWS/FT546MWS` are verified but are not committed unless that repository is restored by the user. + +--- + +## File Structure + +- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyRecoveryEvidence.java`: immutable database-shaped recovery projection only. +- Modify `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java`: expose the local-evidence query. +- Modify `ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml`: select the first incomplete step and its active/finished job, stock, cargo-space, and lock evidence. +- Modify `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowRecoveryService.java`: implement the approved decision table without HTTP. +- Modify `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransaction.java`: remove the special `UNKNOWN` callback branch while retaining the shared idempotent advancement path. +- Delete `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskState.java`: obsolete WCS state DTO. +- Delete `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskStateClient.java`: obsolete HTTP adapter. +- Delete `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowState.java`: obsolete WCS-oriented recovery projection. +- Modify `fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/AlgorithmProperties.java`: remove only task-state URL/timeout properties. +- Modify `ruoyi-admin/src/main/resources/application.yml`: remove only task-state configuration. +- Modify focused JUnit tests under `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms`: prove SQL shape, every recovery decision, callback idempotency, and dependency removal. +- Modify `D:/workSpace/PROJECT_SPACE/FT546/tools/history-replay/Invoke-Ft546TidyBatchReplay.ps1`: replace the WCS UNKNOWN scenario with repairable-finish and missing-local-job cases. +- Modify `D:/workSpace/PROJECT_SPACE/FT546/tools/history-replay/simulators/ft546-wcs-simulator.mjs`: remove task-state simulator state and endpoints. +- Modify `D:/workSpace/PROJECT_SPACE/FT546/tools/history-replay/tests/Invoke-Ft546TidyBatchReplay.Tests.ps1`: enforce the new replay contract. +- Modify historical design/plan notes, the root handoff, and `tools/history-replay/README.md`: explain supersession and point to new evidence. + +### Task 1: WMS Local Recovery Evidence Projection + +**Files:** +- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyRecoveryEvidence.java` +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java` +- Modify: `ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml` +- Test: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapperWorkflowSqlTest.java` + +**Interfaces:** +- Produces: `List selectTidyRecoveryEvidence(String warehouseId)`. +- Produces: nullable `stepId`/`stepNo` when every step is complete; all count fields are non-null `int` values. +- Produces: job fields from the deterministic ID `TDY--` even when the row disagrees with the step tuple. + +- [ ] **Step 1: Write failing SQL contract tests** + +Add these tests to `WmsAlgorithmMapperWorkflowSqlTest`: + +```java +@Test +void tidyRecoveryProjectionMapsWorkflowJobInventoryAndLockEvidence() throws IOException { + String mapping = statement("resultmap", "tidyRecoveryEvidenceMap"); + + assertThat(mapping) + .contains("type=\"com.ruoyi.algorithm.wms.tidy.tidyrecoveryevidence\"") + .contains("column=\"workflowid\"") + .contains("column=\"stepid\"") + .contains("column=\"activejobid\"") + .contains("column=\"finishedjobid\"") + .contains("column=\"sourcestockrows\"") + .contains("column=\"targetstockrows\"") + .contains("column=\"activelockcount\""); +} + +@Test +void tidyRecoveryQueryUsesOnlyWmsLocalAuthoritativeTables() throws IOException { + String query = statement("select", "selectTidyRecoveryEvidence"); + + assertThat(query) + .contains("from alg.tidy_workflow workflow") + .contains("outer apply") + .contains("from alg.tidy_step step") + .contains("left join dbo.mydo_job active_job") + .contains("left join dbo.mydo_job_finish finished_job") + .contains("from dbo.bas_stock") + .contains("left join dbo.bas_cargo_space source_space") + .contains("left join dbo.bas_cargo_space target_space") + .contains("from alg.tidy_space_lock") + .contains("concat('tdy-', workflow.workflow_id, '-', active_step.step_id)") + .doesNotContain("wcs_task_id"); +} +``` + +- [ ] **Step 2: Run the focused test and confirm the red state** + +Run from `D:/workSpace/PROJECT_SPACE/FT546/FT546MWS/FT546MWS`: + +```powershell +mvn -pl ruoyi-admin -am "-Dtest=WmsAlgorithmMapperWorkflowSqlTest" "-Dsurefire.failIfNoSpecifiedTests=false" test +``` + +Expected: FAIL because `tidyRecoveryEvidenceMap` and `selectTidyRecoveryEvidence` do not exist. + +- [ ] **Step 3: Add the immutable projection and mapper signature** + +Create `TidyRecoveryEvidence.java` exactly as a data-only record: + +```java +package com.ruoyi.algorithm.wms.tidy; + +public record TidyRecoveryEvidence( + String workflowId, + TidyOrigin origin, + String workflowStatus, + int activeLockCount, + String stepId, + Integer stepNo, + String stepStatus, + String sourceSpaceId, + String targetSpaceId, + String trayCode, + int activeTidyJobCount, + String activeJobId, + String activeJobStatus, + String activeJobInOrOut, + String activeJobTrayCode, + String activeJobSourceSpaceId, + String activeJobTargetSpaceId, + String finishedJobId, + String finishedJobStatus, + String finishedJobInOrOut, + String finishedJobTrayCode, + String finishedJobSourceSpaceId, + String finishedJobTargetSpaceId, + int sourceStockRows, + int targetStockRows, + String sourceSpaceStatus, + String sourceSpaceTrayCode, + String targetSpaceStatus, + String targetSpaceTrayCode) { } +``` + +Import it in `WmsAlgorithmMapper` and add: + +```java +List selectTidyRecoveryEvidence(@Param("warehouseId") String warehouseId); +``` + +- [ ] **Step 4: Implement the database-shaped result map and local-evidence SQL** + +Replace the old recovery result map/query only after adding this new query, so the project remains compilable between tasks. The SQL must use `OUTER APPLY` so a workflow whose steps are all complete is still returned: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +- [ ] **Step 5: Run the mapper test and commit the projection** + +```powershell +mvn -pl ruoyi-admin -am "-Dtest=WmsAlgorithmMapperWorkflowSqlTest" "-Dsurefire.failIfNoSpecifiedTests=false" test +git add ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyRecoveryEvidence.java ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapperWorkflowSqlTest.java +git commit --only -m "feat: project local tidy recovery evidence" ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyRecoveryEvidence.java ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapperWorkflowSqlTest.java +``` + +Expected: the focused test passes and the commit contains only the four listed files. + +### Task 2: Transactional Recovery Decision Service + +**Files:** +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowRecoveryService.java` +- Test: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowRecoveryServiceTest.java` + +**Interfaces:** +- Consumes: `List selectTidyRecoveryEvidence(String warehouseId)` from Task 1. +- Consumes: `boolean TidyWorkflowTransaction.onJobFinished(String jobId, String callbackStage)` with `callbackStage="RECOVERY_FINISHED"`. +- Produces: `int reconcile(String warehouseId)`, counting workflows changed by completion repair, terminal cleanup, or pause; no-op and duplicate repairs return zero. + +- [ ] **Step 1: Replace WCS-oriented unit tests with the local decision table** + +Use a test builder that creates a matching active job in the valid pre-move state by default. The test class must include these assertions: + +```java +private WmsAlgorithmMapper mapper; +private TidyWorkflowTransaction transaction; +private TidyWorkflowRecoveryService service; + +@BeforeEach +void setUp() { + mapper = mock(WmsAlgorithmMapper.class); + transaction = mock(TidyWorkflowTransaction.class); + service = new TidyWorkflowRecoveryService(mapper, transaction); +} + +@Test +void matchingActiveJobWithPreMoveEvidenceRemainsRunning() { + TidyRecoveryEvidence evidence = matching().build(); + when(mapper.selectTidyRecoveryEvidence("01")).thenReturn(List.of(evidence)); + + assertThat(service.reconcile("01")).isZero(); + + verifyNoInteractions(transaction); + verify(mapper, never()).pauseTidyWorkflow(anyString(), anyString()); + verify(mapper, never()).releaseTidyResources(anyString()); +} + +@Test +void matchingArchivedFinishAdvancesOnceWithoutMovingInventoryAgain() { + TidyRecoveryEvidence evidence = matching().finished().build(); + when(mapper.selectTidyRecoveryEvidence("01")).thenReturn(List.of(evidence)); + when(transaction.onJobFinished(evidence.finishedJobId(), "RECOVERY_FINISHED")) + .thenReturn(true, false); + + assertThat(service.reconcile("01")).isEqualTo(1); + assertThat(service.reconcile("01")).isZero(); + + verify(transaction, times(2)).onJobFinished(evidence.finishedJobId(), "RECOVERY_FINISHED"); + verify(mapper, never()).releaseTidyResources(evidence.workflowId()); +} + +@Test +void completedStepsWithoutAnActiveTidyJobFinishWorkflowAndReleaseLocks() { + TidyRecoveryEvidence evidence = matching().allStepsCompleted().build(); + when(mapper.selectTidyRecoveryEvidence("01")).thenReturn(List.of(evidence)); + + assertThat(service.reconcile("01")).isEqualTo(1); + + verify(mapper).completeTidyWorkflow(evidence.workflowId()); + verify(mapper).releaseTidyResources(evidence.workflowId()); +} + +@ParameterizedTest(name = "{0}") +@MethodSource("pauseCases") +void inconsistentLocalEvidencePausesWithoutReleasingLocks( + String label, TidyRecoveryEvidence evidence, String expectedReason) { + when(mapper.selectTidyRecoveryEvidence("01")).thenReturn(List.of(evidence)); + + assertThat(service.reconcile("01")).isEqualTo(1); + + verify(mapper).pauseTidyWorkflow(evidence.workflowId(), expectedReason); + verify(mapper).pauseTidyStep(evidence.workflowId(), evidence.stepNo(), expectedReason); + verify(mapper, never()).releaseTidyResources(anyString()); + verifyNoInteractions(transaction); +} + +private static Stream pauseCases() { + return Stream.of( + Arguments.of("invalid active inventory", matching().sourceStockRows(0).build(), + "RECOVERY_ACTIVE_EVIDENCE_INVALID"), + Arguments.of("missing active and finish jobs", matching().missingJobs().build(), + "RECOVERY_JOB_MISSING"), + Arguments.of("active job type mismatch", matching().activeJobInOrOut("27").build(), + "RECOVERY_JOB_MISMATCH"), + Arguments.of("active job tray mismatch", matching().activeJobTrayCode("OTHER").build(), + "RECOVERY_JOB_MISMATCH"), + Arguments.of("active job source mismatch", matching().activeJobSourceSpaceId("OTHER").build(), + "RECOVERY_JOB_MISMATCH"), + Arguments.of("finish status is not terminal", matching().finished().finishedJobStatus("3").build(), + "RECOVERY_FINISH_EVIDENCE_INVALID"), + Arguments.of("finish job target mismatch", matching().finished().finishedJobTargetSpaceId("OTHER").build(), + "RECOVERY_JOB_MISMATCH"), + Arguments.of("finish inventory is not terminal", matching().finished().sourceStockRows(1).build(), + "RECOVERY_FINISH_EVIDENCE_INVALID"), + Arguments.of("active and finish rows coexist", matching().withBothJobs().build(), + "RECOVERY_FINISH_EVIDENCE_INVALID")); +} + +@Test +void alreadyPausedWorkflowIsLeftUntouched() { + TidyRecoveryEvidence evidence = matching().workflowStatus("PAUSED_RECOVERY").build(); + when(mapper.selectTidyRecoveryEvidence("01")).thenReturn(List.of(evidence)); + + assertThat(service.reconcile("01")).isZero(); + + verifyNoInteractions(transaction); + verify(mapper, never()).pauseTidyWorkflow(anyString(), anyString()); +} + +@Test +void queryFailurePropagatesWithoutWritingRecoveryState() { + when(mapper.selectTidyRecoveryEvidence("01")).thenThrow(new IllegalStateException("db unavailable")); + + assertThatThrownBy(() -> service.reconcile("01")) + .isInstanceOf(IllegalStateException.class) + .hasMessage("db unavailable"); + + verify(mapper, never()).pauseTidyWorkflow(anyString(), anyString()); + verifyNoInteractions(transaction); +} +``` + +Define the referenced builder in the same test class. Its defaults represent a valid pre-move state, while its named mutations create each exact decision-table row: + +```java +private static EvidenceBuilder matching() { + return new EvidenceBuilder(); +} + +private static final class EvidenceBuilder { + private String workflowId = "WF-1"; + private TidyOrigin origin = TidyOrigin.MANUAL_TIDY; + private String workflowStatus = "RUNNING"; + private int activeLockCount = 2; + private String stepId = "WF-1-1"; + private Integer stepNo = 1; + private String stepStatus = "PENDING"; + private String sourceSpaceId = "SOURCE"; + private String targetSpaceId = "TARGET"; + private String trayCode = "TRAY-1"; + private int activeTidyJobCount = 1; + private String activeJobId = "TDY-WF-1-WF-1-1"; + private String activeJobStatus = "1"; + private String activeJobInOrOut = "28"; + private String activeJobTrayCode = "TRAY-1"; + private String activeJobSourceSpaceId = "SOURCE"; + private String activeJobTargetSpaceId = "TARGET"; + private String finishedJobId; + private String finishedJobStatus; + private String finishedJobInOrOut; + private String finishedJobTrayCode; + private String finishedJobSourceSpaceId; + private String finishedJobTargetSpaceId; + private int sourceStockRows = 1; + private int targetStockRows; + private String sourceSpaceStatus = "2"; + private String sourceSpaceTrayCode = "TRAY-1"; + private String targetSpaceStatus = "1"; + private String targetSpaceTrayCode = ""; + + EvidenceBuilder workflowStatus(String value) { + workflowStatus = value; + return this; + } + + EvidenceBuilder activeJobInOrOut(String value) { + activeJobInOrOut = value; + return this; + } + + EvidenceBuilder activeJobTrayCode(String value) { + activeJobTrayCode = value; + return this; + } + + EvidenceBuilder activeJobSourceSpaceId(String value) { + activeJobSourceSpaceId = value; + return this; + } + + EvidenceBuilder sourceStockRows(int value) { + sourceStockRows = value; + return this; + } + + EvidenceBuilder finishedJobStatus(String value) { + finishedJobStatus = value; + return this; + } + + EvidenceBuilder finishedJobTargetSpaceId(String value) { + finishedJobTargetSpaceId = value; + return this; + } + + EvidenceBuilder finished() { + activeTidyJobCount = 0; + activeJobId = null; + activeJobStatus = null; + activeJobInOrOut = null; + activeJobTrayCode = null; + activeJobSourceSpaceId = null; + activeJobTargetSpaceId = null; + finishedJobId = "TDY-WF-1-WF-1-1"; + finishedJobStatus = "4"; + finishedJobInOrOut = "28"; + finishedJobTrayCode = trayCode; + finishedJobSourceSpaceId = sourceSpaceId; + finishedJobTargetSpaceId = targetSpaceId; + sourceStockRows = 0; + targetStockRows = 1; + sourceSpaceStatus = "1"; + sourceSpaceTrayCode = ""; + targetSpaceStatus = "2"; + targetSpaceTrayCode = trayCode; + return this; + } + + EvidenceBuilder missingJobs() { + activeTidyJobCount = 0; + activeJobId = null; + activeJobStatus = null; + activeJobInOrOut = null; + activeJobTrayCode = null; + activeJobSourceSpaceId = null; + activeJobTargetSpaceId = null; + finishedJobId = null; + finishedJobStatus = null; + finishedJobInOrOut = null; + finishedJobTrayCode = null; + finishedJobSourceSpaceId = null; + finishedJobTargetSpaceId = null; + return this; + } + + EvidenceBuilder withBothJobs() { + finishedJobId = activeJobId; + finishedJobStatus = "4"; + finishedJobInOrOut = "28"; + finishedJobTrayCode = trayCode; + finishedJobSourceSpaceId = sourceSpaceId; + finishedJobTargetSpaceId = targetSpaceId; + return this; + } + + EvidenceBuilder allStepsCompleted() { + missingJobs(); + stepId = null; + stepNo = null; + stepStatus = null; + sourceSpaceId = null; + targetSpaceId = null; + trayCode = null; + sourceStockRows = 0; + targetStockRows = 0; + sourceSpaceStatus = null; + sourceSpaceTrayCode = null; + targetSpaceStatus = null; + targetSpaceTrayCode = null; + return this; + } + + TidyRecoveryEvidence build() { + return new TidyRecoveryEvidence( + workflowId, origin, workflowStatus, activeLockCount, + stepId, stepNo, stepStatus, sourceSpaceId, targetSpaceId, trayCode, + activeTidyJobCount, + activeJobId, activeJobStatus, activeJobInOrOut, activeJobTrayCode, + activeJobSourceSpaceId, activeJobTargetSpaceId, + finishedJobId, finishedJobStatus, finishedJobInOrOut, finishedJobTrayCode, + finishedJobSourceSpaceId, finishedJobTargetSpaceId, + sourceStockRows, targetStockRows, + sourceSpaceStatus, sourceSpaceTrayCode, targetSpaceStatus, targetSpaceTrayCode); + } +} +``` + +- [ ] **Step 2: Run the recovery test and confirm the red state** + +```powershell +mvn -pl ruoyi-admin -am "-Dtest=TidyWorkflowRecoveryServiceTest" "-Dsurefire.failIfNoSpecifiedTests=false" test +``` + +Expected: compilation or test failure because the service still requires `WcsTaskStateClient` and calls `selectActiveTidyWorkflows`. + +- [ ] **Step 3: Implement the minimal transactional decision service** + +Replace `TidyWorkflowRecoveryService` with this decision structure; keep all comparison helpers in this service, not in the projection: + +```java +@Service +public class TidyWorkflowRecoveryService { + private static final Set ACTIVE_JOB_STATUSES = Set.of("1", "2", "3"); + + private final WmsAlgorithmMapper mapper; + private final TidyWorkflowTransaction transaction; + + public TidyWorkflowRecoveryService(WmsAlgorithmMapper mapper, + TidyWorkflowTransaction transaction) { + this.mapper = mapper; + this.transaction = transaction; + } + + @Transactional(rollbackFor = Exception.class) + public int reconcile(String warehouseId) { + int changed = 0; + for (TidyRecoveryEvidence evidence : mapper.selectTidyRecoveryEvidence(warehouseId)) { + if ("PAUSED_RECOVERY".equals(evidence.workflowStatus())) { + continue; + } + if (evidence.stepId() == null) { + if (evidence.activeTidyJobCount() == 0) { + mapper.completeTidyWorkflow(evidence.workflowId()); + mapper.releaseTidyResources(evidence.workflowId()); + changed++; + } else { + changed += pause(evidence, "RECOVERY_JOB_MISMATCH"); + } + continue; + } + if (evidence.activeJobId() != null && evidence.finishedJobId() != null) { + changed += pause(evidence, "RECOVERY_FINISH_EVIDENCE_INVALID"); + continue; + } + if (evidence.activeJobId() != null) { + if (!matchesStep(evidence, false)) { + changed += pause(evidence, "RECOVERY_JOB_MISMATCH"); + } else if (ACTIVE_JOB_STATUSES.contains(evidence.activeJobStatus()) + && isPreMoveState(evidence)) { + continue; + } else { + changed += pause(evidence, "RECOVERY_ACTIVE_EVIDENCE_INVALID"); + } + continue; + } + if (evidence.finishedJobId() != null) { + if (!matchesStep(evidence, true)) { + changed += pause(evidence, "RECOVERY_JOB_MISMATCH"); + } else if ("4".equals(evidence.finishedJobStatus()) + && isTerminalMoveState(evidence)) { + if (transaction.onJobFinished(evidence.finishedJobId(), "RECOVERY_FINISHED")) { + changed++; + } + } else { + changed += pause(evidence, "RECOVERY_FINISH_EVIDENCE_INVALID"); + } + continue; + } + changed += pause(evidence, "RECOVERY_JOB_MISSING"); + } + return changed; + } + + private boolean matchesStep(TidyRecoveryEvidence evidence, boolean finished) { + return "28".equals(finished ? evidence.finishedJobInOrOut() : evidence.activeJobInOrOut()) + && Objects.equals(evidence.trayCode(), finished + ? evidence.finishedJobTrayCode() : evidence.activeJobTrayCode()) + && Objects.equals(evidence.sourceSpaceId(), finished + ? evidence.finishedJobSourceSpaceId() : evidence.activeJobSourceSpaceId()) + && Objects.equals(evidence.targetSpaceId(), finished + ? evidence.finishedJobTargetSpaceId() : evidence.activeJobTargetSpaceId()); + } + + private boolean isPreMoveState(TidyRecoveryEvidence evidence) { + return evidence.sourceStockRows() > 0 + && evidence.targetStockRows() == 0 + && "2".equals(evidence.sourceSpaceStatus()) + && Objects.equals(evidence.trayCode(), trimToNull(evidence.sourceSpaceTrayCode())) + && "1".equals(evidence.targetSpaceStatus()) + && trimToNull(evidence.targetSpaceTrayCode()) == null; + } + + private boolean isTerminalMoveState(TidyRecoveryEvidence evidence) { + return evidence.sourceStockRows() == 0 + && evidence.targetStockRows() > 0 + && "1".equals(evidence.sourceSpaceStatus()) + && trimToNull(evidence.sourceSpaceTrayCode()) == null + && "2".equals(evidence.targetSpaceStatus()) + && Objects.equals(evidence.trayCode(), trimToNull(evidence.targetSpaceTrayCode())); + } + + private int pause(TidyRecoveryEvidence evidence, String reason) { + mapper.pauseTidyWorkflow(evidence.workflowId(), reason); + if (evidence.stepNo() != null) { + mapper.pauseTidyStep(evidence.workflowId(), evidence.stepNo(), reason); + } + return 1; + } + + private String trimToNull(String value) { + if (value == null || value.trim().isEmpty()) return null; + return value.trim(); + } +} +``` + +Add imports for `java.util.Objects` and `java.util.Set`. Do not catch mapper exceptions and do not call `releaseTidyResources` from `pause`. + +- [ ] **Step 4: Run service, transaction, and mapper tests** + +```powershell +mvn -pl ruoyi-admin -am "-Dtest=TidyWorkflowRecoveryServiceTest,TidyWorkflowTransactionTest,WmsAlgorithmMapperWorkflowSqlTest" "-Dsurefire.failIfNoSpecifiedTests=false" test +``` + +Expected: PASS; Mockito proves no lock release on every paused path and no inventory procedure is reachable from the service. + +- [ ] **Step 5: Commit the recovery decision** + +```powershell +git add ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowRecoveryService.java ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowRecoveryServiceTest.java +git commit --only -m "feat: recover tidy workflows from WMS evidence" ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowRecoveryService.java ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowRecoveryServiceTest.java +``` + +### Task 3: Remove WCS Task-State and UNKNOWN Callback Dependencies + +**Files:** +- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/WcsTaskStateDependencyRemovalTest.java` +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransaction.java` +- Modify: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransactionTest.java` +- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java` +- Modify: `ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml` +- Modify: `fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/AlgorithmProperties.java` +- Modify: `ruoyi-admin/src/main/resources/application.yml` +- Delete: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskState.java` +- Delete: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskStateClient.java` +- Delete: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowState.java` + +**Interfaces:** +- Retains: `tidyWcsSnapshotUrl` and its getter/setter. +- Removes: task-state URL, timeout, DTO, HTTP client, old mapper projection/query, and the `UNKNOWN` callback branch. +- Retains: `RECOVERY_FINISHED` on the same callback-event idempotency key used by normal completion. + +- [ ] **Step 1: Add a failing absence test and a positive recovery-stage test** + +Create `WcsTaskStateDependencyRemovalTest`: + +```java +class WcsTaskStateDependencyRemovalTest { + @Test + void taskStateClassesPropertiesAndConfigurationAreAbsent() throws Exception { + assertThatThrownBy(() -> Class.forName( + "com.ruoyi.algorithm.wms.tidy.WcsTaskStateClient")) + .isInstanceOf(ClassNotFoundException.class); + assertThatThrownBy(() -> Class.forName( + "com.ruoyi.algorithm.wms.tidy.WcsTaskState")) + .isInstanceOf(ClassNotFoundException.class); + + assertThat(Arrays.stream(AlgorithmProperties.class.getMethods()) + .map(Method::getName)) + .noneMatch(name -> name.contains("TidyWcsTaskState")); + + try (InputStream stream = getClass().getResourceAsStream("/application.yml")) { + assertThat(stream).isNotNull(); + String yaml = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + assertThat(yaml).doesNotContain("tidy-wcs-task-state"); + assertThat(yaml).contains("tidy-wcs-snapshot-url"); + } + } +} +``` + +Replace the old `unknownCallbackPausesWorkflowAndKeepsResources` transaction test with: + +```java +@Test +void recoveryFinishedUsesTheNormalIdempotentAdvancementPath() { + WmsAlgorithmMapper mapper = mock(WmsAlgorithmMapper.class); + ExecutionAdmissionService admission = mock(ExecutionAdmissionService.class); + TidyWorkflowTransaction tx = new TidyWorkflowTransaction(mapper, admission); + when(mapper.selectTidyWorkflowIdByJob("JOB-1")).thenReturn("WF-1"); + when(mapper.selectTidyStepIdByJob("JOB-1")).thenReturn("STEP-1"); + when(mapper.insertTidyCallbackEventIfAbsent(any(), eq("WF-1"), eq("STEP-1"), + eq("JOB-1"), eq("RECOVERY_FINISHED"), eq("SUCCESS"))).thenReturn(1); + when(mapper.selectNextTidyStepId("WF-1")).thenReturn(null); + + assertThat(tx.onJobFinished("JOB-1", "RECOVERY_FINISHED")).isTrue(); + + verify(mapper).completeTidyStep("STEP-1"); + verify(mapper).completeTidyWorkflow("WF-1"); + verify(mapper).releaseTidyResources("WF-1"); + verify(mapper, never()).pauseTidyWorkflow(anyString(), anyString()); +} +``` + +- [ ] **Step 2: Run clean focused tests and confirm the absence test is red** + +```powershell +mvn -pl ruoyi-admin -am clean "-Dtest=WcsTaskStateDependencyRemovalTest,TidyWorkflowTransactionTest" "-Dsurefire.failIfNoSpecifiedTests=false" test +``` + +Expected: FAIL because task-state classes, properties, and YAML keys still exist. + +- [ ] **Step 3: Delete the dependency and obsolete recovery projection** + +Delete the three listed Java files. Remove these members from `AlgorithmProperties` and nothing related to the snapshot URL: + +```java +private String tidyWcsTaskStateUrl; +private int tidyWcsTaskStateTimeoutMs; +public String getTidyWcsTaskStateUrl(); +public void setTidyWcsTaskStateUrl(String value); +public int getTidyWcsTaskStateTimeoutMs(); +public void setTidyWcsTaskStateTimeoutMs(int value); +``` + +Remove these two YAML entries: + +```yaml +tidy-wcs-task-state-url: ${FORTUCKY_TIDY_WCS_TASK_STATE_URL:http://localhost:8081/wcs2Wms/api/tidyTaskState} +tidy-wcs-task-state-timeout-ms: ${FORTUCKY_TIDY_WCS_TASK_STATE_TIMEOUT_MS:3000} +``` + +Remove `selectActiveTidyWorkflows`, its `TidyWorkflowState` import/signature, `tidyWorkflowStateMap`, and the old XML query. In `TidyWorkflowTransaction`, remove only: + +```java +if ("UNKNOWN".equals(callbackStage)) { + mapper.pauseTidyWorkflow(workflowId, "PAUSED_RECOVERY"); + return true; +} +``` + +- [ ] **Step 4: Verify dependency removal and callback regressions** + +```powershell +mvn -pl ruoyi-admin -am clean "-Dtest=WcsTaskStateDependencyRemovalTest,TidyWorkflowRecoveryServiceTest,TidyWorkflowTransactionTest,ToWmsServiceImplWorkflowCallbackTest" "-Dsurefire.failIfNoSpecifiedTests=false" test +rg -n "WcsTaskState|tidyWcsTaskState|tidy-wcs-task-state|FORTUCKY_TIDY_WCS_TASK_STATE|UNKNOWN.*callbackStage" fortucky-algorithm/src/main ruoyi-admin/src/main +``` + +Expected: Maven PASS. The search prints no matches. A separate search for `tidy-wcs-snapshot-url` still finds the retained snapshot configuration. + +- [ ] **Step 5: Commit only WMS dependency-removal files** + +```powershell +git add fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/AlgorithmProperties.java ruoyi-admin/src/main/resources/application.yml ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransaction.java ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransactionTest.java ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/WcsTaskStateDependencyRemovalTest.java +git add -u ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskState.java ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskStateClient.java ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowState.java +git commit --only -m "refactor: remove WCS tidy task-state recovery" fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/AlgorithmProperties.java ruoyi-admin/src/main/resources/application.yml ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransaction.java ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransactionTest.java ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/WcsTaskStateDependencyRemovalTest.java ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskState.java ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskStateClient.java ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowState.java +``` + +### Task 4: Replace UNKNOWN Replay with WMS-Local Recovery Cases + +**Files:** +- Modify: `D:/workSpace/PROJECT_SPACE/FT546/tools/history-replay/Invoke-Ft546TidyBatchReplay.ps1` +- Modify: `D:/workSpace/PROJECT_SPACE/FT546/tools/history-replay/simulators/ft546-wcs-simulator.mjs` +- Test: `D:/workSpace/PROJECT_SPACE/FT546/tools/history-replay/tests/Invoke-Ft546TidyBatchReplay.Tests.ps1` + +**Interfaces:** +- Produces replay event `TIDY_RECOVERY_FINISHED` for archived completion repaired after restart. +- Produces replay event `TIDY_RECOVERY_MISSING_JOB` for a run-scoped deleted active job paused with retained locks. +- Produces two `tidy-reconciliation.csv` rows with phases `RECOVERY_FINISHED` and `RECOVERY_MISSING_JOB`. +- Removes simulator endpoints `/sim/task-state` and `/sim/unknown-task` and environment variable `FORTUCKY_TIDY_WCS_TASK_STATE_URL`. + +- [ ] **Step 1: Change Pester expectations first** + +In the automatic/manual control test, replace the old positive task-state assertions with: + +```powershell +$source | Should Match 'TIDY_RECOVERY_FINISHED' +$source | Should Match 'TIDY_RECOVERY_MISSING_JOB' +$source | Should Match 'RECOVERY_FINISHED' +$source | Should Match 'RECOVERY_JOB_MISSING' +$source | Should Match 'usp_complete_move_job' +$source | Should Match 'DELETE FROM dbo\.mydo_job' +$source | Should Not Match 'FORTUCKY_TIDY_WCS_TASK_STATE_URL' +$source | Should Not Match "Invoke-Simulator '/sim/unknown-task'" + +$simulator = Get-Content -LiteralPath $simulatorPath -Raw +$simulator | Should Not Match '/sim/task-state' +$simulator | Should Not Match '/sim/unknown-task' +``` + +Add a contract assertion that the runner inspects `/sim/events` and requires zero task-state paths after both recovery cases. + +- [ ] **Step 2: Run Pester and confirm the red state** + +Run from `D:/workSpace/PROJECT_SPACE/FT546`: + +```powershell +Invoke-Pester .\tools\history-replay\tests\Invoke-Ft546TidyBatchReplay.Tests.ps1 -Output Detailed +``` + +Expected: FAIL on the old environment variable, simulator endpoints, and missing local-recovery events. + +- [ ] **Step 3: Remove task-state behavior from the simulator and WMS process environment** + +Delete `unknownNextTask`, both task-state routes, and the reset assignment from `ft546-wcs-simulator.mjs`. Keep vehicle snapshot, receive-job, completion, event, and reset routes unchanged. Remove only this environment entry from `Start-WmsProcess`: + +```powershell +FORTUCKY_TIDY_WCS_TASK_STATE_URL = "http://127.0.0.1:$SimulatorPort/sim/task-state" +``` + +- [ ] **Step 4: Add run-scoped SQL helpers for the two crash boundaries** + +Add these helpers beside `Complete-TidyWorkflow`: + +```powershell +function Complete-TidyJobWithoutCallback([string]$JobId) { + $jobLiteral = ConvertTo-SqlLiteral $JobId + $result = @(Invoke-Query @" +DECLARE @result int; +EXEC alg.usp_complete_move_job @v_job_id=$jobLiteral, @v_result=@result OUTPUT; +SELECT @result AS result_code; +"@) + if ($result.Count -ne 1 -or [int]$result[0].result_code -ne 10) { + throw "Crash-boundary tidy completion failed for $JobId" + } +} + +function Remove-ActiveTidyJob([string]$JobId) { + $deleted = [int](Invoke-NonQuery "DELETE FROM dbo.mydo_job WHERE job_id=$(ConvertTo-SqlLiteral $JobId) AND in_or_out='28'") + if ($deleted -ne 1) { + throw "Expected one active tidy job to be removed for $JobId; deleted=$deleted" + } +} +``` + +These mutations run only after the verified backup. They must be recorded with `Add-Event` and are cleaned by the existing `finally` restore. + +- [ ] **Step 5: Implement repairable archived-completion recovery** + +Replace the old UNKNOWN block with this ordered flow: + +1. Start WMS with a fast automatic scan and wait for a fresh `AUTO_TIDY` workflow and its active `TDY-%` job. +2. Stop WMS. +3. Capture the tray/source/target and current matching `move_msg` count. +4. Call `Complete-TidyJobWithoutCallback`; assert the active row is gone, one status `4` finish row exists, the tidy step is still non-complete, stock is at target, source is empty, and target holds the tray. +5. Capture the post-procedure `move_msg` count, restart WMS with automatic scan quiet and reconciliation at 1000 ms, then wait for the step/workflow to advance. +6. Assert exactly one `RECOVERY_FINISHED` callback event, no active deterministic job, unchanged post-procedure `move_msg` count, and zero active locks when the workflow is terminal. +7. Write `TIDY_RECOVERY_FINISHED` and a passing `RECOVERY_FINISHED` reconciliation row. + +Use these acceptance predicates in the runner: + +```powershell +$repairPass = [string]$repairAfter.status -eq 'COMPLETED' -and + [int]$repairAfter.active_jobs -eq 0 -and + [int]$repairAfter.finish_jobs -eq 1 -and + [int]$repairAfter.recovery_events -eq 1 -and + [int]$repairAfter.active_locks -eq 0 -and + [int]$repairAfter.move_messages -eq $moveMessagesAfterProcedure +``` + +- [ ] **Step 6: Implement missing-local-job recovery** + +1. Start WMS with a fast automatic scan and wait for another fresh workflow/job, excluding the normal and repaired workflow IDs. +2. Stop WMS and call `Remove-ActiveTidyJob`. +3. Restart WMS with automatic scan quiet and reconciliation at 1000 ms. +4. Wait for workflow status `PAUSED_RECOVERY`, workflow `status_reason='RECOVERY_JOB_MISSING'`, at least one paused step, and active lock count greater than zero. +5. Assert no replacement deterministic job and no `RECOVERY_FINISHED` event. +6. Write `TIDY_RECOVERY_MISSING_JOB` and a passing `RECOVERY_MISSING_JOB` reconciliation row. + +Use these acceptance predicates: + +```powershell +$missingPass = [string]$missingAfter.status -eq 'PAUSED_RECOVERY' -and + [string]$missingAfter.status_reason -eq 'RECOVERY_JOB_MISSING' -and + [int]$missingAfter.paused_steps -ge 1 -and + [int]$missingAfter.active_locks -gt 0 -and + [int]$missingAfter.active_jobs -eq 0 -and + [int]$missingAfter.recovery_events -eq 0 +``` + +Finally query simulator events and fail the run unless both task-state paths are absent: + +```powershell +$simulatorEvents = @((Invoke-Simulator '/sim/events').data) +$taskStateRequests = @($simulatorEvents | Where-Object { + $_.path -in @('/sim/task-state', '/sim/unknown-task') +}).Count +if ($taskStateRequests -ne 0) { + throw "Unexpected WCS task-state requests observed: $taskStateRequests" +} +``` + +- [ ] **Step 7: Run Pester to green** + +```powershell +Invoke-Pester .\tools\history-replay\tests\Invoke-Ft546TidyBatchReplay.Tests.ps1 -Output Detailed +``` + +Expected: all contract tests pass; the exact total may increase from the historical 25 as the two new contracts are added. + +No Git commit is attempted for this task because `D:/workSpace/PROJECT_SPACE/FT546` is not a Git repository. + +### Task 5: Documentation, Full Regression, and Final Replay Evidence + +**Files:** +- Modify: `docs/superpowers/specs/2026-07-22-ft546-tidy-and-batch-replay-design.md` +- Modify: `docs/superpowers/plans/2026-07-22-ft546-tidy-and-batch-replay.md` +- Modify: `D:/workSpace/PROJECT_SPACE/FT546/tools/history-replay/README.md` +- Modify: `D:/workSpace/PROJECT_SPACE/FT546/docs/FT546-WMS-history-replay-handoff-2026-07-22.md` +- Generate: `D:/workSpace/PROJECT_SPACE/FT546/outputs/ft546-tidy-batch-replay-/report.md` + +**Interfaces:** +- Historical documents remain intact except for a dated supersession note linking to the approved 2026-07-23 design and this implementation plan. +- The handoff reports the new run ID, recovery evidence, unit/Pester totals, final JAR hash, and restoration result. + +- [ ] **Step 1: Add supersession notes before changing historical claims** + +At the WCS recovery sections in both 2026-07-22 documents, add: + +```markdown +> Superseded on 2026-07-23: WCS task-state lookup and the UNKNOWN-task replay case are replaced by WMS-native evidence recovery. See `docs/superpowers/specs/2026-07-23-wms-native-tidy-recovery-design.md` and `docs/superpowers/plans/2026-07-23-wms-native-tidy-recovery.md`. Vehicle snapshot behavior is not superseded. +``` + +Do not rewrite the historical task list or pretend its old acceptance evidence used the new design. + +- [ ] **Step 2: Run focused and full WMS verification** + +From `D:/workSpace/PROJECT_SPACE/FT546/FT546MWS/FT546MWS`: + +```powershell +$env:MAVEN_OPTS='-Xms64m -Xmx384m -XX:MaxMetaspaceSize=192m -XX:ReservedCodeCacheSize=64m -XX:+UseSerialGC' +mvn -pl ruoyi-admin -am "-Dtest=TidyWorkflowRecoveryServiceTest,TidyWorkflowTransactionTest,WcsTaskStateDependencyRemovalTest,WmsAlgorithmMapperWorkflowSqlTest,ToWmsServiceImplWorkflowCallbackTest" "-Dsurefire.failIfNoSpecifiedTests=false" test +mvn -q "-DargLine=-Xms32m -Xmx128m -XX:MaxMetaspaceSize=96m -XX:ReservedCodeCacheSize=32m -XX:+UseSerialGC" test +mvn -DskipTests package +``` + +Expected: all focused tests pass, the root reactor has zero failure/error/skipped tests, and packaging exits 0. + +- [ ] **Step 3: Run replay contract tests and read-only preflight** + +From `D:/workSpace/PROJECT_SPACE/FT546`: + +```powershell +Invoke-Pester .\tools\history-replay\tests\Invoke-Ft546TidyBatchReplay.Tests.ps1 -Output Detailed +pwsh -File .\tools\history-replay\Invoke-Ft546TidyBatchReplay.ps1 -WmsBaseUrl http://127.0.0.1:18082 -SimulatorPort 18081 -PreflightOnly +``` + +Expected: Pester passes and preflight creates a new output directory without changing the database. + +- [ ] **Step 4: Run the destructive replay under the existing verified-backup guard** + +Confirm ports 18081/18082 are unused and do not stop the user's WMS on 18080. Then run: + +```powershell +pwsh -File .\tools\history-replay\Invoke-Ft546TidyBatchReplay.ps1 -WmsBaseUrl http://127.0.0.1:18082 -SimulatorPort 18081 -ConfirmReplay +``` + +Do not use `-KeepFailedState`. If backup creation, restore, or baseline verification fails, stop further writes and preserve the run output for DBA review. + +- [ ] **Step 5: Verify the new run artifacts rather than trusting the exit code** + +Inspect the newest run directory and require all of the following: + +```text +summary.completed = true +summary.restoration.passed = true +RESTORE event mismatches = [] +TIDY_RECOVERY_FINISHED pass = true +TIDY_RECOVERY_MISSING_JOB pass = true +RECOVERY_FINISHED callback event count = 1 for the repair workflow +missing-job workflow status_reason = RECOVERY_JOB_MISSING +missing-job active lock count > 0 before restore +simulator task-state request count = 0 +``` + +Also confirm `tidy-reconciliation.csv` contains the unchanged automatic/manual rows plus both new recovery rows and that ports 18081/18082 have no listener after cleanup. + +- [ ] **Step 6: Update the replay README and handoff with measured evidence** + +Replace the handoff's WCS UNKNOWN claim with the two WMS-local cases. Record exact output paths, test totals, final JAR size/hash, backup path, restoration result, and any remaining limitations. Do not reuse the 2026-07-23 04:29 evidence as proof of the new recovery implementation. + +- [ ] **Step 7: Commit only WMS repository documentation and perform final checks** + +From `D:/workSpace/PROJECT_SPACE/FT546/FT546MWS/FT546MWS`: + +```powershell +git add docs/superpowers/specs/2026-07-22-ft546-tidy-and-batch-replay-design.md docs/superpowers/plans/2026-07-22-ft546-tidy-and-batch-replay.md +git commit --only -m "docs: supersede WCS tidy task-state recovery" docs/superpowers/specs/2026-07-22-ft546-tidy-and-batch-replay-design.md docs/superpowers/plans/2026-07-22-ft546-tidy-and-batch-replay.md +git diff --check 590eea6..HEAD +git status --short +``` + +Expected: `git diff --check` prints nothing. `git status --short` may still show the user's pre-existing changes, but none were reset, unstaged, or included in these focused commits. diff --git a/docs/superpowers/specs/2026-07-23-wms-native-tidy-recovery-design.md b/docs/superpowers/specs/2026-07-23-wms-native-tidy-recovery-design.md index 10f76f3..1b0376e 100644 --- a/docs/superpowers/specs/2026-07-23-wms-native-tidy-recovery-design.md +++ b/docs/superpowers/specs/2026-07-23-wms-native-tidy-recovery-design.md @@ -1,7 +1,7 @@ # WMS-Native Tidy Recovery Design > Date: 2026-07-23 -> Status: Approved direction, awaiting written-spec review +> Status: Approved > Supersedes: the WCS task-state recovery portions of `2026-07-22-ft546-tidy-and-batch-replay-design.md` ## Context