docs: plan tidy workflow implementation

This commit is contained in:
zgw
2026-07-22 10:37:31 +08:00
parent b7ee435d38
commit 0cc4596f07

View File

@@ -0,0 +1,822 @@
# FT546 Tidy Workflow and Historical Batch Replay 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:** Add persisted automatic/manual tidy execution with exclusive manual mode, then prove inbound, outbound, relocation, and tidy correctness by replaying site history through simulated PDA/WCS interfaces.
**Architecture:** Keep `TidyPlanner` pure in `fortucky-algorithm` and add WMS-specific persistence/orchestration under `ruoyi-admin/com/ruoyi/algorithm/wms/tidy`. SQL Server is authoritative for mode, queues, workflows, callback events, and locks; all legacy and algorithm allocation routes pass through one admission service. WCS exposes a read-only vehicle snapshot, while a run-scoped simulator and PowerShell harness drive the development-database replay and reconciliation.
**Tech Stack:** Java 17, Spring Boot, Spring transactions/scheduling, MyBatis, SQL Server, JUnit 5, Mockito, AssertJ, Vue 2 + Element UI, Node.js built-in HTTP server, PowerShell 7/System.Data.SqlClient.
## Global Constraints
- Work in `D:/workSpace/PROJECT_SPACE/FT546/FT546MWS/FT546MWS` for WMS and `D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS` for WCS; both are dirty repositories, so never revert or stage unrelated changes.
- Preserve the `fortucky-algorithm` module boundary and the configured WMS primary `DataSource`/`PlatformTransactionManager`.
- Keep the normal deployment default `FORTUCKY_ALGORITHM_MODE=SHADOW`; only the confirmed development database may use the run-scoped replay override.
- Automatic tidy runs only in `NORMAL`, never alongside inbound/outbound/outbound-relocation work, and has a global maximum of one active workflow.
- Manual mode follows `NORMAL -> ENTERING_TIDY -> TIDY_ACTIVE -> EXITING_TIDY -> NORMAL`; business documents remain accepted but execution allocation is blocked outside `NORMAL`.
- Manual capacity is `online + healthy + (idle or already assigned to manual tidy)` minus active manual workflows; safety predicates and resource locks override capacity filling.
- Every move job with `in_or_out=28` must resolve to exactly one origin: `AUTO_TIDY`, `MANUAL_TIDY`, or `OUTBOUND_RELOCATION`.
- Do not delete or mutate imported finish tables. Before live replay, create and verify a new restorable backup after migrations; restore it after evidence generation.
- Implement every production change test-first and commit only exact task paths.
---
## File Structure
### WMS algorithm and persistence
- Modify `fortucky-algorithm/src/main/resources/db/sqlserver/install.sql`: idempotent outbound/tidy schema, queue handoff, resource locking, and sequence synchronization procedures.
- Modify `fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics.sql`: mode/workflow/queue/origin/lock integrity diagnostics.
- Modify `fortucky-algorithm/src/main/resources/db/sqlserver/uninstall.sql`: reverse only new algorithm objects in dependency order.
- Modify `fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/SqlServerMigrationScriptTest.java`: migration contract tests.
- Modify `fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/AlgorithmProperties.java`: tidy scan/reconcile/lease and WCS snapshot URL configuration.
- Modify `fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/SchedulingFacade.java`: expose `planTidy` without adding WMS persistence to the algorithm module.
### WMS tidy runtime
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyEffectiveState.java`: persisted mode enum.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyOrigin.java`: job/workflow origin enum.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ExecutionWorkType.java`: gate input enum.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyModeSnapshot.java`: immutable status DTO.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowState.java`: mapper projection for workflow/step state.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/VehicleSnapshot.java`: per-vehicle WCS contract.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyVehicleCapacity.java`: capacity calculation.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyModeService.java`: switch transitions and startup reconciliation.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ExecutionAdmissionService.java`: shared business/tidy admission gate.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ExecutionBlockedByTidyModeException.java`: stable blocked-allocation exception.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowStarter.java`: scheduler-to-coordinator boundary.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskState.java`: restart-reconciliation WCS task state.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransaction.java`: transactional claim, job creation, callback, recovery, and release.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowCoordinator.java`: automatic singleton and manual capacity filling.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyScheduler.java`: 10-minute automatic scan and short mode reconciliation.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsVehicleSnapshotClient.java`: production WCS snapshot adapter.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ReplayExecutionGuard.java`: validates a run-scoped ACTIVE process against `DB_NAME()` before writes.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java` and `ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml`: all tidy SQL operations and origin queries.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsInboundAllocationService.java` and `WmsOutboundAllocationService.java`: shared gate before algorithm and legacy paths.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/OutboundWorkflowTransaction.java`: origin linkage and shared resource admission.
- Modify `ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ToWmsServiceImpl.java`: route idempotent job-finish callbacks to outbound then tidy coordinators.
### WCS capacity endpoint
- Create `FT546-WCS/ruoyi-admin/src/main/java/com/ruoyi/api/domain/wcs2wms/SrgvVehicleSnapshot.java`: read-only transport DTO.
- Modify `FT546-WCS/ruoyi-admin/src/main/java/com/ruoyi/api/controller/Wcs2WmsController.java`: `POST /wcs2Wms/api/tidyVehicleSnapshot`.
- Modify `FT546-WCS/ruoyi-admin/src/main/java/com/ruoyi/api/service/IApiService.java` and `impl/ApiServiceImpl.java`: derive online/healthy/idle state from `device_srgv`.
### Operator control
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/controller/TidyControlController.java`: authenticated status and switch API.
- Create `ruoyi-admin/src/main/java/com/ruoyi/algorithm/controller/TidyModeRequest.java`: switch request DTO.
- Create `ruoyi-ui/src/api/algorithm/tidy.js`: status/switch calls.
- Create `ruoyi-ui/src/views/algorithm/tidy/index.vue`: compact operational status and switch page.
- Create `sql/algorithm_tidy_menu.sql`: idempotent dynamic menu and permissions.
### Replay and evidence
- Create `tools/history-replay/simulators/ft546-wcs-simulator.mjs`: three-vehicle WCS snapshot/task simulator with controllable failures.
- Create `tools/history-replay/sql/select-batch-fixtures.sql`: deterministic eligible history and mapping decisions.
- Create `tools/history-replay/sql/reconcile-tidy-batch.sql`: per-run inventory, cargo-space, workflow, origin, and lock evidence.
- Create `tools/history-replay/Invoke-Ft546TidyBatchReplay.ps1`: backup, migration, fixture execution, HTTP simulation, reconciliation, reports, and restore.
- Modify `tools/history-replay/README.md`: exact preflight/run/resume commands and artifact contract.
---
### Task 1: Add Idempotent SQL Server Tidy Schema
**Files:**
- Modify: `fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/SqlServerMigrationScriptTest.java`
- Modify: `fortucky-algorithm/src/main/resources/db/sqlserver/install.sql`
- Modify: `fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics.sql`
- Modify: `fortucky-algorithm/src/main/resources/db/sqlserver/uninstall.sql`
**Interfaces:**
- Produces: `alg.tidy_mode`, `alg.tidy_lane_queue`, `alg.tidy_workflow`, `alg.tidy_step`, `alg.tidy_space_lock`, `alg.tidy_callback_event`.
- Produces: `alg.usp_claim_tidy_lane`, `alg.usp_release_tidy_lane`, `alg.usp_sync_replay_sequences`.
- Preserves: existing `alg.dirty_lane_queue` as the rebuild queue and hands rebuilt lanes to `alg.tidy_lane_queue` only after successful rebuild.
- [ ] **Step 1: Extend migration contract tests and verify red**
Add assertions that require these exact fragments:
```java
assertThat(install).contains("CREATE TABLE alg.tidy_mode");
assertThat(install).contains("CREATE TABLE alg.tidy_lane_queue");
assertThat(install).contains("CREATE TABLE alg.tidy_workflow");
assertThat(install).contains("CREATE TABLE alg.tidy_step");
assertThat(install).contains("CREATE TABLE alg.tidy_space_lock");
assertThat(install).contains("CREATE TABLE alg.tidy_callback_event");
assertThat(install).contains("CREATE PROCEDURE alg.usp_claim_tidy_lane");
assertThat(install).contains("sp_getapplock");
assertThat(install).contains("OUTBOUND_RELOCATION");
assertThat(diagnostics).contains("PAUSED_RECOVERY");
assertThat(uninstall).contains("DROP TABLE alg.tidy_mode");
```
Run:
```bash
mvn -pl fortucky-algorithm -Dtest=SqlServerMigrationScriptTest test
```
Expected: FAIL because tidy objects do not exist.
- [ ] **Step 2: Add tables, keys, constraints, and queue handoff**
Use these persisted values:
```sql
effective_state varchar(24) CHECK (effective_state IN
('NORMAL','ENTERING_TIDY','TIDY_ACTIVE','EXITING_TIDY'))
origin varchar(24) CHECK (origin IN ('AUTO_TIDY','MANUAL_TIDY'))
workflow_status varchar(24) CHECK (workflow_status IN
('CLAIMED','WAITING_DISPATCH','DISPATCHED','RUNNING','PAUSED_RECOVERY','COMPLETED','CANCELLED_RECONCILED'))
resource_type varchar(8) CHECK (resource_type IN ('SPACE','LANE','FLOOR'))
```
Create filtered unique indexes for active resources, WCS task IDs, callback event keys, and singleton active automatic workflow. Add `origin='OUTBOUND_RELOCATION'` to `alg.outbound_workflow`. At the successful end of `alg.usp_refresh_dirty_lanes`, `MERGE` each row from `#dirty` into `alg.tidy_lane_queue`, incrementing `dirty_version`; retain existing catch/requeue behavior.
- [ ] **Step 3: Add claim/release and replay-sequence procedures**
`alg.usp_claim_tidy_lane` must claim one `PENDING` row using `UPDLOCK, READPAST, ROWLOCK`, ordered by `last_dirty_at, warehouse_id, lane_code`, and set a lease token/expiry. `alg.usp_release_tidy_lane` must clear only the claimed version; if a newer version exists, return it to `PENDING`.
`alg.usp_sync_replay_sequences` must run under `sp_getapplock`, compare active and finish tables, and advance `bas_job_id`/`bas_task_no` without decreasing them. It must include `CK202607210002` as a regression fixture in the SQL test.
- [ ] **Step 4: Verify migration scripts**
Run:
```bash
mvn -pl fortucky-algorithm -Dtest=SqlServerMigrationScriptTest test
```
Expected: PASS with all install/diagnostics/uninstall assertions green.
- [ ] **Step 5: Commit only migration files**
```bash
git add fortucky-algorithm/src/main/resources/db/sqlserver/install.sql \
fortucky-algorithm/src/main/resources/db/sqlserver/diagnostics.sql \
fortucky-algorithm/src/main/resources/db/sqlserver/uninstall.sql \
fortucky-algorithm/src/test/java/com/ruoyi/algorithm/repository/SqlServerMigrationScriptTest.java
git commit -m "feat: add persisted tidy workflow schema"
```
### Task 2: Implement Persisted Mode and Shared Admission
**Files:**
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyEffectiveState.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ExecutionWorkType.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyModeSnapshot.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyModeService.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ExecutionAdmissionService.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyModeServiceTest.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/ExecutionAdmissionServiceTest.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java`
- Modify: `ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml`
**Interfaces:**
- Produces: `TidyModeSnapshot status(String warehouseId)`.
- Produces: `TidyModeSnapshot requestManualMode(String warehouseId, boolean enabled, String actor)`.
- Produces: `TidyModeSnapshot reconcile(String warehouseId)`.
- Produces: `void assertAllowed(String warehouseId, ExecutionWorkType workType)`.
- Consumes mapper operations: `selectTidyModeForUpdate`, `setTidyModeRequest`, `countActiveBusinessExecution`, `countActiveManualTidy`.
- [ ] **Step 1: Write failing state-machine and admission tests**
Cover the exact table:
```java
assertThat(service.requestManualMode("01", true, "tester").effectiveState())
.isEqualTo(TidyEffectiveState.ENTERING_TIDY);
when(mapper.countActiveBusinessExecution("01")).thenReturn(0);
assertThat(service.reconcile("01").effectiveState())
.isEqualTo(TidyEffectiveState.TIDY_ACTIVE);
assertThatThrownBy(() -> gate.assertAllowed("01", ExecutionWorkType.INBOUND))
.isInstanceOf(ExecutionBlockedByTidyModeException.class);
gate.assertAllowed("01", ExecutionWorkType.MANUAL_TIDY);
```
Also test idempotent repeated enable/disable, `EXITING_TIDY` drain, optimistic-version failure, and acceptance of `DOCUMENT_RECEIPT` in every state.
Run:
```bash
mvn -pl ruoyi-admin -Dtest='TidyModeServiceTest,ExecutionAdmissionServiceTest' test
```
Expected: FAIL because types/services/mappers are missing.
- [ ] **Step 2: Add the domain types and service logic**
Use this gate contract:
```java
public enum ExecutionWorkType {
DOCUMENT_RECEIPT, INBOUND, OUTBOUND, OUTBOUND_RELOCATION, AUTO_TIDY, MANUAL_TIDY;
public boolean allowedIn(TidyEffectiveState state) {
if (this == DOCUMENT_RECEIPT) return true;
if (state == TidyEffectiveState.NORMAL) return this != MANUAL_TIDY;
return state == TidyEffectiveState.TIDY_ACTIVE && this == MANUAL_TIDY;
}
}
public final class ExecutionAdmissionService {
private final TidyModeService modeService;
public ExecutionAdmissionService(TidyModeService modeService) {
this.modeService = modeService;
}
public void assertAllowed(String warehouseId, ExecutionWorkType workType) {
TidyEffectiveState state = modeService.status(warehouseId).effectiveState();
if (!workType.allowedIn(state)) {
throw new ExecutionBlockedByTidyModeException(state.name());
}
}
}
```
`NORMAL` allows business execution and auto tidy; `ENTERING_TIDY` and `EXITING_TIDY` allow only document receipt and drain callbacks; `TIDY_ACTIVE` allows document receipt, manual tidy, and callbacks. State changes and counts must be read inside the WMS transaction manager.
- [ ] **Step 3: Implement mapper state locking and status projection**
`selectTidyModeForUpdate` uses `WITH (UPDLOCK,HOLDLOCK)`. The status projection includes requested switch, effective state, drain count, active tidy count, status reason, version, updater, and timestamp.
- [ ] **Step 4: Run focused tests**
```bash
mvn -pl ruoyi-admin -Dtest='TidyModeServiceTest,ExecutionAdmissionServiceTest,WmsAlgorithmMapperRegistrationTest' test
```
Expected: PASS.
- [ ] **Step 5: Commit exact mode files**
```bash
git add ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyEffectiveState.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ExecutionWorkType.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyModeSnapshot.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyModeService.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ExecutionAdmissionService.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ExecutionBlockedByTidyModeException.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/tidy/TidyModeServiceTest.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/ExecutionAdmissionServiceTest.java
git commit -m "feat: persist tidy mode admission state"
```
### Task 3: Gate Every Inbound and Outbound Allocation Route
**Files:**
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsInboundAllocationService.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsOutboundAllocationService.java`
- Modify: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsInboundAllocationServiceTest.java`
- Modify: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsOutboundAllocationServiceTest.java`
- Modify: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/AlgorithmShadowConfigurationTest.java`
**Interfaces:**
- Consumes: `ExecutionAdmissionService.assertAllowed(warehouseId, INBOUND|OUTBOUND)`.
- Guarantees: the gate runs before both `SchedulingFacade` planning and legacy stored-procedure fallback.
- [ ] **Step 1: Add failing router tests**
For `allocate`, `reallocate`, `allocateProject`, and `allocateExactStock`, make the gate throw and verify neither algorithm nor legacy mapper is invoked:
```java
doThrow(new ExecutionBlockedByTidyModeException("TIDY_ACTIVE"))
.when(admission).assertAllowed("01", ExecutionWorkType.OUTBOUND);
assertThatThrownBy(() -> service.allocateProject(stockout, detail))
.isInstanceOf(ExecutionBlockedByTidyModeException.class);
verifyNoInteractions(facade, stockoutMapper, workflowCoordinator);
```
Run:
```bash
mvn -pl ruoyi-admin -Dtest='WmsInboundAllocationServiceTest,WmsOutboundAllocationServiceTest' test
```
Expected: FAIL because constructors and gate calls are absent.
- [ ] **Step 2: Inject and call the shared gate**
Call the gate once at the top of each shared router after resolving warehouse ID and before any reservation release, planner call, or legacy procedure. Do not add controller-only checks. `ToWmsServiceImpl.reMaterial` remains covered because it routes through `OutStockoutServiceImpl.autoAllot -> WmsOutboundAllocationService.allocateProject`.
- [ ] **Step 3: Verify callback paths remain admitted**
Add tests proving `jobFinish`, `completeInbound`, outbound confirmation, and existing in-flight advancement do not call the creation gate. They are drain/recovery operations and must remain executable in non-normal states.
- [ ] **Step 4: Run WMS allocation regression**
```bash
mvn -pl ruoyi-admin -Dtest='WmsInboundAllocationServiceTest,InboundAllocationTransactionTest,WmsOutboundAllocationServiceTest,OutboundWorkflowTransactionTest,PersistentOutboundWorkflowCoordinatorTest,AlgorithmShadowConfigurationTest' test
```
Expected: PASS.
- [ ] **Step 5: Commit allocation gate changes**
```bash
git add ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsInboundAllocationService.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsOutboundAllocationService.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsInboundAllocationServiceTest.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/WmsOutboundAllocationServiceTest.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/AlgorithmShadowConfigurationTest.java
git commit -m "feat: gate allocation during manual tidy"
```
### Task 4: Add Persistent Tidy-Lane Scheduling
**Files:**
- Modify: `fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/AlgorithmProperties.java`
- Modify: `fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/SchedulingFacade.java`
- Modify: `fortucky-algorithm/src/test/java/com/ruoyi/algorithm/integration/AlgorithmConfigurationTest.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyLaneClaim.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowStarter.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ReplayExecutionGuard.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyScheduler.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidySchedulerTest.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/ReplayExecutionGuardTest.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java`
- Modify: `ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml`
- Modify: `ruoyi-admin/src/main/resources/application.yml`
**Interfaces:**
- Produces: `TidyPlan SchedulingFacade.planTidy(String warehouseId, Instant now)`.
- Produces: `Optional<TidyLaneClaim> claimTidyLane(String warehouseId, String owner, Instant expiresAt)`.
- Produces: `UUID TidyWorkflowStarter.startAutomatic(TidyLaneClaim claim, TidyCandidate candidate)` as the scheduler boundary; Task 5 makes the coordinator implement it.
- [ ] **Step 1: Add failing scheduler tests**
Test these cases explicitly: not `NORMAL`; active inbound; active outbound relocation; one active tidy; no queue claim; stale candidate; successful single start; lease failure requeues.
```java
when(mode.status("01")).thenReturn(normal);
when(mapper.countActiveBusinessExecution("01")).thenReturn(1);
scheduler.scanAutomatic();
verifyNoInteractions(coordinator);
```
Run:
```bash
mvn -pl fortucky-algorithm -Dtest=AlgorithmConfigurationTest test
mvn -pl ruoyi-admin -Dtest=TidySchedulerTest test
```
Expected: FAIL because facade/scheduler APIs are absent.
- [ ] **Step 2: Expose pure tidy planning and properties**
Add properties with these defaults:
```yaml
fortucky:
algorithm:
tidy-auto-scan-ms: 600000
tidy-reconcile-ms: 5000
tidy-claim-lease-seconds: 60
tidy-wcs-snapshot-url: ${FORTUCKY_TIDY_WCS_SNAPSHOT_URL:http://localhost:8081/wcs2Wms/api/tidyVehicleSnapshot}
replay-run-id: ${FT546_REPLAY_RUN_ID:}
replay-database: ${FT546_REPLAY_DATABASE:}
```
`SchedulingFacade.planTidy` may run in `SHADOW` for diagnostics, but workflow creation still requires the run-scoped write gate and `ExecutionWorkType.AUTO_TIDY`.
`ReplayExecutionGuard` runs at application startup. When `replay-run-id` is nonblank, it requires routing mode `ACTIVE`, database writes enabled, and mapper `selectDatabaseName()` exactly equal to `replay-database`; otherwise startup fails before schedulers or HTTP writers run.
- [ ] **Step 3: Implement scheduler and versioned queue mapper**
Use `@Scheduled(fixedDelayString = "${fortucky.algorithm.tidy-auto-scan-ms:600000}")`. Lock/claim the oldest queue row, invoke the planner, select only a candidate touching the claimed lane, and release/requeue using the claim token and dirty version.
- [ ] **Step 4: Run planner and scheduler suites**
```bash
mvn -pl fortucky-algorithm -Dtest='TidyPlannerTest,WarehouseApplicationServiceTest,AlgorithmConfigurationTest' test
mvn -pl ruoyi-admin -Dtest='TidySchedulerTest,ReplayExecutionGuardTest,WmsAlgorithmMapperRegistrationTest' test
```
Expected: PASS.
- [ ] **Step 5: Commit scheduling changes**
```bash
git add fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/AlgorithmProperties.java \
fortucky-algorithm/src/main/java/com/ruoyi/algorithm/integration/SchedulingFacade.java \
fortucky-algorithm/src/test/java/com/ruoyi/algorithm/integration/AlgorithmConfigurationTest.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyLaneClaim.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowStarter.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/ReplayExecutionGuard.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyScheduler.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidySchedulerTest.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/ReplayExecutionGuardTest.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java \
ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml \
ruoyi-admin/src/main/resources/application.yml
git commit -m "feat: schedule automatic tidy from dirty lanes"
```
### Task 5: Execute Tidy Through the Existing Move-Job Chain
**Files:**
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyOrigin.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowState.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskState.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransaction.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowCoordinator.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransactionTest.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowCoordinatorTest.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/WmsAlgorithmMapper.java`
- Modify: `ruoyi-admin/src/main/resources/mapper/algorithm/WmsAlgorithmMapper.xml`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/OutboundWorkflowTransaction.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ToWmsServiceImpl.java`
**Interfaces:**
- Produces: `UUID start(TidyOrigin origin, String warehouseId, TidyCandidate candidate)`.
- Produces: `boolean onJobFinished(String jobId, String callbackStage)`.
- Produces: `TidyWorkflowState reconcile(UUID workflowId, WcsTaskState taskState)`.
- Implements: `UUID TidyWorkflowStarter.startAutomatic(TidyLaneClaim claim, TidyCandidate candidate)`.
- Guarantees: one `TidyMove` becomes one ordered `in_or_out=28` job; only the next dependent step is active.
- [ ] **Step 1: Add failing workflow transaction tests**
Cover: current pallet/space revalidation; transaction-scoped `sp_getapplock`; disjoint `SPACE/LANE/FLOOR` resources; ordered multi-move candidate; one job per move; duplicate callback event; out-of-order rejection; terminal release; `PAUSED_RECOVERY` retained locks.
Define tidy workflow origin narrowly:
```java
public enum TidyOrigin {
AUTO_TIDY, MANUAL_TIDY
}
```
Outbound workflows persist the separate constant `OUTBOUND_RELOCATION`; job-origin queries union the two owning workflow types and never pass that value into `TidyWorkflowTransaction.start`.
```java
UUID workflow = transaction.start(TidyOrigin.MANUAL_TIDY, "01", candidate);
verify(mapper).insertTidyMoveJob(argThat(step -> step.inOrOut() == 28));
assertThat(transaction.onJobFinished("TIDY-1", "FINISHED")).isTrue();
assertThat(transaction.onJobFinished("TIDY-1", "FINISHED")).isFalse();
verify(mapper, times(1)).completeTidyStep(anyString());
```
Run:
```bash
mvn -pl ruoyi-admin -Dtest='TidyWorkflowTransactionTest,TidyWorkflowCoordinatorTest' test
```
Expected: FAIL because workflow types/mapper methods are missing.
- [ ] **Step 2: Implement transactional workflow creation**
Inside one `@Transactional` method:
```java
admission.assertAllowed(warehouseId,
origin == TidyOrigin.AUTO_TIDY ? ExecutionWorkType.AUTO_TIDY : ExecutionWorkType.MANUAL_TIDY);
mapper.acquireAlgorithmResourceAppLock(warehouseId, resourceKeys);
mapper.revalidateTidyCandidate(candidate);
mapper.insertTidyWorkflow(...);
mapper.insertTidySteps(...);
mapper.lockTidyResources(...);
mapper.insertTidyMoveJob(firstStep);
```
Each subsequent callback inserts `alg.tidy_callback_event` first. A duplicate unique-key event returns the current state without applying inventory/job transitions twice.
- [ ] **Step 3: Link outbound relocation origin and callback routing**
Persist `OUTBOUND_RELOCATION` on outbound workflows. Extend job query DTOs by workflow join. In `ToWmsServiceImpl.jobFinish`, try the existing outbound coordinator first, then the tidy coordinator only when outbound returns `noMatch`; callbacks for unrelated jobs retain existing behavior.
- [ ] **Step 4: Run outbound and tidy workflow regression**
```bash
mvn -pl ruoyi-admin -Dtest='OutboundWorkflowTransactionTest,PersistentOutboundWorkflowCoordinatorTest,WmsAlgorithmMapperWorkflowSqlTest,TidyWorkflowTransactionTest,TidyWorkflowCoordinatorTest' test
```
Expected: PASS, including `N MOVE_OUT + OUTBOUND + N RESTORE` assertions and tidy callback idempotency.
- [ ] **Step 5: Commit workflow files**
```bash
git add ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyOrigin.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowState.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsTaskState.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransaction.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowCoordinator.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowTransactionTest.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowCoordinatorTest.java \
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/OutboundWorkflowTransaction.java \
ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ToWmsServiceImpl.java
git commit -m "feat: execute persisted tidy move workflows"
```
### Task 6: Add WCS Vehicle Snapshot and Manual Capacity Filling
**Files:**
- Create: `D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS/ruoyi-admin/src/main/java/com/ruoyi/api/domain/wcs2wms/SrgvVehicleSnapshot.java`
- Modify: `D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS/ruoyi-admin/src/main/java/com/ruoyi/api/controller/Wcs2WmsController.java`
- Modify: `D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS/ruoyi-admin/src/main/java/com/ruoyi/api/service/IApiService.java`
- Modify: `D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS/ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ApiServiceImpl.java`
- Create: `D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS/ruoyi-admin/src/test/java/com/ruoyi/api/service/ApiServiceVehicleSnapshotTest.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/VehicleSnapshot.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyVehicleCapacity.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsVehicleSnapshotClient.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyVehicleCapacityTest.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyModeRecoveryTest.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyModeService.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowCoordinator.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyScheduler.java`
**Interfaces:**
- WCS produces: `List<SrgvVehicleSnapshot> tidyVehicleSnapshot()`.
- WMS consumes: `List<VehicleSnapshot> fetch()`.
- Produces: nested `TidyVehicleCapacity.CapacityResult` and `CapacityResult calculate(List<VehicleSnapshot> vehicles, int activeManualWorkflows, Set<String> assignedManualVehicleIds)`, where `newSlots=max(0,effectiveCapacity-activeManualWorkflows)`.
- [ ] **Step 1: Write capacity and mode-recovery tests**
Use exactly these semantics:
```java
VehicleSnapshot healthyIdle = new VehicleSnapshot("1", true, true, true, null);
VehicleSnapshot healthyTidy = new VehicleSnapshot("2", true, true, false, "MANUAL_TIDY");
VehicleSnapshot busyInbound = new VehicleSnapshot("3", true, true, false, "INBOUND");
CapacityResult result = capacity.calculate(
List.of(healthyIdle, healthyTidy, busyInbound), 1, Set.of("2"));
assertThat(result.effectiveCapacity()).isEqualTo(2);
assertThat(result.newSlots()).isEqualTo(1);
```
Test `ENTERING_TIDY` drain to active, continuous replacement, capacity reduction without cancelling running work, switch-off cancellation of undispatched work, restart at all three non-normal checkpoints, and unknown WCS task -> `PAUSED_RECOVERY`. With three effective vehicles but only two mutually safe candidates, assert two workflows plus status reason `NO_SAFE_CANDIDATE`; the switch must remain enabled.
- [ ] **Step 2: Add the WCS read-only endpoint**
Derive fields from `device_srgv`:
```java
boolean online = "1".equals(device.getAgvStatus());
boolean healthy = online && value(device.getAlarm()) <= 1 && value(device.getErrMsg()) == 0;
boolean idle = healthy && value(device.getTaskNo()) == 0
&& value(device.getLocked()) == 0 && value(device.getChargeFlag()) == 0;
```
Return stable vehicle ID and current task number/type. Add `ApiServiceVehicleSnapshotTest` for offline, alarmed, charging, locked, busy, and idle vehicles. Do not write WCS device or queue state from this endpoint.
- [ ] **Step 3: Add the WMS client and capacity reconciliation**
Use configurable `${fortucky.algorithm.tidy-wcs-snapshot-url:http://localhost:8081/wcs2Wms/api/tidyVehicleSnapshot}`. During `TIDY_ACTIVE`, fill `newSlots` with mutually disjoint candidates. During `EXITING_TIDY`, cancel only `CLAIMED/WAITING_DISPATCH` rows without a WCS task and drain dispatched/running rows.
- [ ] **Step 4: Verify WCS and WMS modules**
Run:
```bash
git -C D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS diff --check
mvn -f D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS/pom.xml -pl ruoyi-admin -am -Dtest=ApiServiceVehicleSnapshotTest test
mvn -pl ruoyi-admin -Dtest='TidyVehicleCapacityTest,TidyModeRecoveryTest,TidyWorkflowCoordinatorTest' test
```
Expected: both Maven commands PASS.
- [ ] **Step 5: Commit exact WCS and WMS capacity files separately**
```bash
git -C D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS add \
ruoyi-admin/src/main/java/com/ruoyi/api/domain/wcs2wms/SrgvVehicleSnapshot.java \
ruoyi-admin/src/main/java/com/ruoyi/api/controller/Wcs2WmsController.java \
ruoyi-admin/src/main/java/com/ruoyi/api/service/IApiService.java \
ruoyi-admin/src/main/java/com/ruoyi/api/service/impl/ApiServiceImpl.java \
ruoyi-admin/src/test/java/com/ruoyi/api/service/ApiServiceVehicleSnapshotTest.java
git -C D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS commit -m "feat: expose tidy vehicle capacity"
git add ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/VehicleSnapshot.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyVehicleCapacity.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/WcsVehicleSnapshotClient.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyModeService.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowCoordinator.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/wms/tidy/TidyScheduler.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyVehicleCapacityTest.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyModeRecoveryTest.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/wms/tidy/TidyWorkflowCoordinatorTest.java
git commit -m "feat: fill manual tidy vehicle capacity"
```
### Task 7: Add Operator Switch and Status Page
**Files:**
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/controller/TidyModeRequest.java`
- Create: `ruoyi-admin/src/main/java/com/ruoyi/algorithm/controller/TidyControlController.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/algorithm/controller/TidyControlControllerTest.java`
- Create: `ruoyi-ui/src/api/algorithm/tidy.js`
- Create: `ruoyi-ui/src/views/algorithm/tidy/index.vue`
- Create: `sql/algorithm_tidy_menu.sql`
**Interfaces:**
- `GET /algorithm/tidy/status?warehouseId=01` with permission `algorithm:tidy:query`.
- `PUT /algorithm/tidy/mode` body `{ "warehouseId": "01", "enabled": true }` with permission `algorithm:tidy:edit`.
- [ ] **Step 1: Write failing controller tests**
```java
mockMvc.perform(get("/algorithm/tidy/status").param("warehouseId", "01"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.effectiveState").value("NORMAL"));
mockMvc.perform(put("/algorithm/tidy/mode")
.contentType(APPLICATION_JSON)
.content("{\"warehouseId\":\"01\",\"enabled\":true}"))
.andExpect(status().isOk());
verify(modeService).requestManualMode(eq("01"), eq(true), anyString());
```
Run:
```bash
mvn -pl ruoyi-admin -Dtest=TidyControlControllerTest test
```
Expected: FAIL because controller is absent.
- [ ] **Step 2: Implement secured API and idempotent menu SQL**
Controller methods return `AjaxResult.success(snapshot)` and use `@PreAuthorize("@ss.hasPermi('algorithm:tidy:query')")` / `algorithm:tidy:edit`. The menu script uses `IF NOT EXISTS` by permission string and component `algorithm/tidy/index`.
- [ ] **Step 3: Implement compact operational page**
Use `<el-switch>` with confirmation and rollback on error. Show effective state, requested switch, drain count, active tidy, healthy/available vehicles, candidate count, and last reason. Poll every 5 seconds only while `document.visibilityState === 'visible'`; clear timer/listener in `beforeDestroy`.
- [ ] **Step 4: Verify backend and frontend build**
```bash
mvn -pl ruoyi-admin -Dtest=TidyControlControllerTest test
npm --prefix ruoyi-ui run build:prod
```
Expected: controller PASS and Vue production build exits 0.
- [ ] **Step 5: Browser-check the page**
Start WMS and UI, then use Playwright at desktop `1440x900` and mobile `390x844`. Verify switch confirmation, failed-switch rollback, polling, no overlapping text, and status updates through `ENTERING_TIDY/TIDY_ACTIVE/EXITING_TIDY`.
- [ ] **Step 6: Commit UI/control files**
```bash
git add ruoyi-admin/src/main/java/com/ruoyi/algorithm/controller/TidyModeRequest.java \
ruoyi-admin/src/main/java/com/ruoyi/algorithm/controller/TidyControlController.java \
ruoyi-admin/src/test/java/com/ruoyi/algorithm/controller/TidyControlControllerTest.java \
ruoyi-ui/src/api/algorithm/tidy.js ruoyi-ui/src/views/algorithm/tidy/index.vue \
sql/algorithm_tidy_menu.sql
git commit -m "feat: add manual tidy control page"
```
### Task 8: Build Deterministic PDA/WCS Batch Replay Harness
**Files:**
- Create: `tools/history-replay/simulators/ft546-wcs-simulator.mjs`
- Create: `tools/history-replay/sql/select-batch-fixtures.sql`
- Create: `tools/history-replay/sql/reconcile-tidy-batch.sql`
- Create: `tools/history-replay/Invoke-Ft546TidyBatchReplay.ps1`
- Create: `tools/history-replay/tests/Invoke-Ft546TidyBatchReplay.Tests.ps1`
- Modify: `tools/history-replay/README.md`
**Interfaces:**
- Simulator: `POST /wcs2Wms/api/tidyVehicleSnapshot`, `/receiveInMydoJob`, `/receiveOutMydoJob`, `/sim/vehicles`, `/sim/complete`, `/sim/unknown-task`.
- Runner: `-PreflightOnly`, `-ConfirmReplay`, `-KeepFailedState`, `-WmsBaseUrl`, `-SimulatorPort`.
- Outputs: `inbound-reconciliation.csv`, `outbound-reconciliation.csv`, `tidy-reconciliation.csv`, `events.json`, `report.md`.
- [ ] **Step 1: Write failing Pester/static harness tests**
Require source hashes/counts, closed warehouse mapping, orphan exclusion, deterministic ordering, 100/300 inbound boundary, 4x25 outbound strata, at least 50 planner-selected cases, callback duplication/out-of-order cases, backup verification, run-scoped override removal, and final restore proof.
Run:
```powershell
Invoke-Pester .\tools\history-replay\tests\Invoke-Ft546TidyBatchReplay.Tests.ps1 -Output Detailed
```
Expected: FAIL because runner/simulator/queries do not exist.
- [ ] **Step 2: Implement deterministic fixture SQL**
Eligibility must be expressed in SQL and returned with `mapping_decision`:
```sql
CASE
WHEN NULLIF(LTRIM(RTRIM(source_warehouse)), '') IS NULL THEN 'MAP_BLANK_TO_01'
WHEN source_warehouse = '07' THEN 'MAP_07_TO_01'
WHEN source_warehouse = '01' THEN 'KEEP_01'
ELSE 'UNSUPPORTED_SOURCE_WAREHOUSE'
END
```
Order by completion time, document ID, detail ID. Exclude `CKD202605140015-1`. Never update finish rows.
- [ ] **Step 3: Implement WCS simulator and event journal**
Use Node built-in `http`; default to three online healthy idle vehicles. Every received task and callback gets an immutable sequence number and timestamp. `/sim/unknown-task` makes the next WMS recovery lookup return unknown without deleting the journal.
- [ ] **Step 4: Implement guarded replay orchestration**
Reuse the existing `System.Data.SqlClient` helpers and exact six source-count checks. Flow: preflight -> install migration -> `COPY_ONLY, CHECKSUM` backup -> `RESTORE VERIFYONLY` -> sequence sync -> create run ID -> start simulator -> start an isolated WMS process -> inbound HTTP loop -> outbound just-in-time strata loop -> automatic tidy cases -> manual three-vehicle/capacity/restart cases -> reconcile -> write artifacts -> stop writers -> restore backup -> verify baseline.
The isolated WMS process receives these process-local values and uses a non-conflicting HTTP port:
```powershell
$env:FORTUCKY_ALGORITHM_MODE = 'ACTIVE'
$env:FORTUCKY_ALGORITHM_DATABASE_WRITES = 'true'
$env:FT546_REPLAY_RUN_ID = $runId
$env:FT546_REPLAY_DATABASE = 'FT546-wms'
$env:FORTUCKY_TIDY_WCS_SNAPSHOT_URL = "http://127.0.0.1:$SimulatorPort/wcs2Wms/api/tidyVehicleSnapshot"
```
Before starting Java, the runner proves the resolved JDBC database is exactly `FT546-wms`, no unrelated active work exists, and every generated business identifier carries `$runId`. A `finally` block stops only the process IDs it started and removes these environment values; the normal deployment configuration remains `SHADOW`.
Every HTTP call records request, response code/body, and related DB snapshot. A mismatch marks the run failed but does not remove its row. `-KeepFailedState` skips restore only when explicitly supplied for debugging and prints the verified backup path prominently.
- [ ] **Step 5: Run harness tests and preflight**
```powershell
Invoke-Pester .\tools\history-replay\tests\Invoke-Ft546TidyBatchReplay.Tests.ps1 -Output Detailed
pwsh -File .\tools\history-replay\Invoke-Ft546TidyBatchReplay.ps1 -PreflightOnly
```
Expected: Pester PASS; preflight prints target `FT546-wms`, source counts/hashes, topology count, active-work count, migration status, and no mutations.
- [ ] **Step 6: Commit replay tooling**
```bash
git add tools/history-replay/simulators/ft546-wcs-simulator.mjs \
tools/history-replay/sql/select-batch-fixtures.sql \
tools/history-replay/sql/reconcile-tidy-batch.sql \
tools/history-replay/Invoke-Ft546TidyBatchReplay.ps1 \
tools/history-replay/tests/Invoke-Ft546TidyBatchReplay.Tests.ps1 \
tools/history-replay/README.md
git commit -m "test: add historical tidy batch replay harness"
```
### Task 9: Run Full Regression and Development-Database Replay
**Files:**
- Generate only: `outputs/ft546-tidy-batch-replay-<run-id>/*`
- Update if defects are found: only the owning task files and their tests.
**Interfaces:**
- Consumes all preceding tasks.
- Produces the final pass/fail evidence and restores the imported-history baseline.
- [ ] **Step 1: Run all automated Java and frontend checks**
```bash
mvn -pl fortucky-algorithm,ruoyi-admin -am test
mvn test
mvn package -DskipTests
npm --prefix ruoyi-ui run build:prod
mvn -f D:/workSpace/PROJECT_SPACE/FT546/FT546-WCS/pom.xml -pl ruoyi-admin -am test
```
Expected: all commands exit 0; record exact test totals and package SHA-256 values in `report.md`.
- [ ] **Step 2: Run SQL diagnostics before destructive replay**
Execute `install.sql` and `diagnostics.sql` against the confirmed development `FT546-wms`. Stop on any active job/workflow, orphan lock, invalid topology, wrong database identity, unsupported source count, or failed backup verification.
- [ ] **Step 3: Execute the full replay**
```powershell
pwsh -File .\tools\history-replay\Invoke-Ft546TidyBatchReplay.ps1 -ConfirmReplay
```
Expected acceptance:
```text
Inbound attempted >= 100, mismatches = 0
Outbound successful = 100 (0 blockers=25, 1=25, 2=25, 3+=25)
Planner-selected outbound >= 50
Automatic tidy max concurrency = 1
Manual tidy capacity: 3 -> refill -> 2 -> no unsafe overfill
Duplicate callback side effects = 0
Restart checkpoints = 4/4 passed
Unexpected PAUSED_RECOVERY = 0
Restore verification = PASS
```
- [ ] **Step 4: Investigate failures test-first and rerun from restored baseline**
For each failing reconciliation row, first add the smallest reproducing unit/integration test to the owning task, observe red, implement the fix, run focused green, then rerun the full automated suite and replay. Never patch database evidence manually.
- [ ] **Step 5: Verify final artifacts and clean runtime processes**
Confirm all five artifact files exist and contain the run ID, source mapping, per-row evidence, failures (including zero-count sections), backup path, restore counts, and database fingerprint. Stop WMS, simulator, and temporary Redis processes started by the run; do not stop unrelated user processes.
- [ ] **Step 6: Commit only defect fixes and final documentation**
```bash
git status --short
git diff --check
```
Do not commit `outputs/` unless the repository's existing policy tracks test evidence. Report WMS and WCS commit IDs, test totals, replay run ID, artifact path, and restored database counts.