From 0cc4596f07df72414ec920e7b9ed8d34a7441cac Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Wed, 22 Jul 2026 10:37:31 +0800 Subject: [PATCH] docs: plan tidy workflow implementation --- .../2026-07-22-ft546-tidy-and-batch-replay.md | 822 ++++++++++++++++++ 1 file changed, 822 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-22-ft546-tidy-and-batch-replay.md diff --git a/docs/superpowers/plans/2026-07-22-ft546-tidy-and-batch-replay.md b/docs/superpowers/plans/2026-07-22-ft546-tidy-and-batch-replay.md new file mode 100644 index 0000000..6f9f939 --- /dev/null +++ b/docs/superpowers/plans/2026-07-22-ft546-tidy-and-batch-replay.md @@ -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 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 tidyVehicleSnapshot()`. +- WMS consumes: `List fetch()`. +- Produces: nested `TidyVehicleCapacity.CapacityResult` and `CapacityResult calculate(List vehicles, int activeManualWorkflows, Set 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 `` 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-/*` +- 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.