# Standard WCS Simulation Acceptance 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:** 用真实标准内核和 Adapter 契约完成确定性模拟、第二仓配置、FT546 历史回放、故障恢复、性能和长期稳定性验收。 **Architecture:** 快速领域测试保留在 `ruoyi-wcs`,真实历史语料继续由独立 `javaTest` 工程加载,但它改为调用标准 `ruoyi-wcs` 构建产物,不再用自身的演示调度器证明产品完成。所有长测试输出统一报告、JSON 摘要和 SHA-256 manifest。 **Tech Stack:** Java 8, Maven, JUnit 5, existing `javaTest` historical fixtures, SQL Server read-only replay input, deterministic simulated adapters. ## Global Constraints - 依赖 Plan 4 已完成的标准运行 wiring 和 FT546 Adapter。 - 模拟器必须实现与真实设备相同的 `DeviceCommandGateway` 和 `DeviceSnapshotGateway`。 - 历史车辆、路径、完成时间和状态只用于比较,不作为新调度输入。 - FT546 历史回放允许车辆和路径不同,但业务终点、PLC 语义和安全不变量必须正确。 - 第二套仓库配置不得修改 `ruoyi-wcs` 代码或构建产物。 - 72 小时/100000 任务测试使用固定随机种子并可从失败种子复现。 - 长测试不进入普通 `mvn test`;使用显式 Maven profile 和 JUnit tag。 - 验收报告不得包含数据库密码、PLC IP 密码或 WMS 签名密钥。 --- ### Task 1: Build deterministic simulated adapters and fault injection **Files:** - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/simulation/SimulationClock.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/simulation/SimulatedDeviceGateway.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/simulation/SimulatedDeviceRuntime.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/simulation/DeterministicFaultPlan.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/simulation/FaultType.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/simulation/SimulationResult.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/simulation/StandardWcsSimulationTest.java` **Interfaces:** - `SimulationClock.advance(Duration)` controls all event time. - `DeterministicFaultPlan.atTick(long,FaultType,String deviceId)` returns one immutable plan and injects `DISCONNECT`, `COMMAND_ACK_LOST`, `DUPLICATE_EVENT`, `OUT_OF_ORDER_EVENT`, `COMMAND_REJECTED`, `POSITION_MISMATCH`, `LOAD_MISMATCH`, `MANUAL_MODE`, or `WCS_RESTART`. - `SimulationResult` exposes event digest, metrics, command counts, callback counts, final command/order states, recovery cases, and invariant violations. - Simulated gateway records standard command envelopes and emits standard events only. - [ ] **Step 1: Write failing deterministic replay tests** ```java @Test void sameSeedProducesTheSameEventStream() { SimulationResult first = run(20260804L, 500); SimulationResult second = run(20260804L, 500); assertEquals(first.getEventDigest(), second.getEventDigest()); assertEquals(first.getMetrics(), second.getMetrics()); } @Test void commitTimeoutCreatesUnknownWithoutDuplicateMotion() { SimulationResult result = run(DeterministicFaultPlan.atTick( 12, FaultType.COMMAND_ACK_LOST, "V1")); assertEquals(1, result.motionCommandCount("CMD-1")); assertEquals("UNKNOWN", result.commandState("CMD-1")); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=StandardWcsSimulationTest test` - [ ] **Step 3: Implement the event-driven simulator** Process each tick in fixed order: due fault → device physics → event delivery → snapshot ingest → recovery gate → dispatch → command creation → Outbox send → invariant scan. Sort devices, events, tasks and resources by stable ID before each phase. - [ ] **Step 4: Run GREEN** Run: `mvn -pl ruoyi-wcs -am -Dtest=StandardWcsSimulationTest test` - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/test/java/com/ruoyi/wcs/simulation git commit -m "test(wcs): add deterministic device simulation" ``` ### Task 2: Prove core portability with a second warehouse configuration **Files:** - Create: `ruoyi-wcs/src/test/resources/acceptance/warehouse-b/topology.json` - Create: `ruoyi-wcs/src/test/resources/acceptance/warehouse-b/devices.json` - Create: `ruoyi-wcs/src/test/resources/acceptance/warehouse-b/workflows.json` - Create: `ruoyi-wcs/src/test/resources/acceptance/warehouse-b/policies.json` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/SecondWarehouseConfigurationTest.java` **Interfaces:** - Warehouse B has 4 floors, 6 shuttles, 2 lifts, 8 conveyor ports, and 3 chargers. - Uses the same `TopologyDefinitionProvider`, `WorkflowDefinitionProvider`, `DispatchPolicy`, `PortSelectionPolicy`, and `ChargingPolicy` ports. - [ ] **Step 1: Write a failing no-core-change acceptance test** ```java @Test void runsDifferentWarehouseThroughTheSameCoreArtifact() { AcceptanceResult result = runner.run("acceptance/warehouse-b", 5000); assertEquals(200, result.getCompletedOrders()); assertEquals(0, result.getFailedOrders()); assertEquals(0, result.getInvariantViolations()); assertEquals("ruoyi-wcs", result.getCoreArtifactId()); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=SecondWarehouseConfigurationTest test` - [ ] **Step 3: Add the complete synthetic configuration** Topology includes two independent traffic zones, both lifts, a shared source-floor intersection, capacity-1 chargers, safe wait points, inbound/outbound/relocation/inventory workflows, and policy weights different from FT546. - [ ] **Step 4: Run GREEN and record the core JAR hash** Run: `mvn -pl ruoyi-wcs -am package` Run: `mvn -pl ruoyi-wcs -am -Dtest=SecondWarehouseConfigurationTest test` Record SHA-256 of `ruoyi-wcs/target/ruoyi-wcs-*.jar` in the test report; the same hash is later recorded for FT546. - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/test/resources/acceptance/warehouse-b ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/SecondWarehouseConfigurationTest.java git commit -m "test(wcs): prove second warehouse portability" ``` ### Task 3: Make javaTest replay the real standard WCS core **Files:** - Modify: `javaTest/pom.xml` - Create: `javaTest/src/main/java/com/ft546/wcs/standard/StandardWcsHistoricalReplayAdapter.java` - Create: `javaTest/src/main/java/com/ft546/wcs/standard/StandardWcsReplayResult.java` - Create: `javaTest/src/test/java/com/ft546/wcs/standard/StandardWcsHistoricalReplayTest.java` **Interfaces:** - Adds dependency `com.ruoyi:ruoyi-wcs:3.8.9` to `javaTest`. - Consumes existing `HistoricalQueueLoader` and frozen manifest. - Maps each record to canonical `CreateTransportOrderCommand`; historical `agv_id/path_info/finish_date` remain comparison metadata only. - [ ] **Step 1: Install the core and write a failing replay test** Run: `mvn -pl ruoyi-wcs -am install -DskipTests` Add: ```java @Test void completesFrozenHistoryThroughTheStandardCore() { StandardWcsReplayResult result = adapter.replay(loader.load(classLoader, "shadow/history")); assertEquals(result.getInputOrders(), result.getCompletedOrders()); assertEquals(0, result.getSafetyViolations()); assertEquals(0, result.getDuplicateBusinessEffects()); } ``` - [ ] **Step 2: Run RED** Run: `cd javaTest && mvn -Dtest=StandardWcsHistoricalReplayTest test` Expected: compilation fails because the standard replay adapter does not exist. - [ ] **Step 3: Implement canonical replay mapping** Use record task number as `externalRequestId`, map task type to standard order type, resolve physical source/destination from frozen map data, submit through `TransportOrderUseCase`, and run the real `WcsRuntimeCoordinator` against deterministic simulated adapters until terminal or watchdog failure. - [ ] **Step 4: Produce semantic comparison fields** For each task record input task number, historical vehicle/path/duration, selected standard vehicle/path/duration, final order state, callback effect count, and violation list. Do not assert identical vehicle or path. - [ ] **Step 5: Run GREEN with existing history tests** Run: `cd javaTest && mvn -Dtest=StandardWcsHistoricalReplayTest,HistoricalQueueLoaderTest,HistoricalQueueDatabaseReplayTest test` - [ ] **Step 6: Commit** ```bash git add javaTest/pom.xml javaTest/src/main/java/com/ft546/wcs/standard javaTest/src/test/java/com/ft546/wcs/standard git commit -m "test(javaTest): replay history through standard WCS core" ``` ### Task 4: Add PLC sample corpus regression **Files:** - Create: `ruoyi-admin/src/test/resources/ft546/plc/corpus/manifest.json` - Create: `ruoyi-admin/src/test/resources/ft546/plc/corpus/srgv-command-cases.json` - Create: `ruoyi-admin/src/test/resources/ft546/plc/corpus/srgv-two-stage-cases.json` - Create: `ruoyi-admin/src/test/resources/ft546/plc/corpus/srgv-snapshot-cases.json` - Create: `ruoyi-admin/src/test/resources/ft546/plc/corpus/lift-cases.json` - Create: `ruoyi-admin/src/test/resources/ft546/plc/corpus/conveyor-cases.json` - Create: `ruoyi-admin/src/test/resources/ft546/plc/corpus/charger-cases.json` - Create: `ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/Ft546PlcCorpusRegressionTest.java` **Interfaces:** - Each corpus case contains sanitized input facts, expected standard command/event, expected addresses, and expected certainty `NOT_SENT/ACCEPTED/ACCEPTED_UNKNOWN/REJECTED`. - Manifest stores row counts and SHA-256 for every sample file. - The manifest emits `Ft546CapabilityEvidence` only when every required point-to-point case passes and, when `TWO_STAGE_ATOMIC_TASK` is requested, every two-stage case passes for the exact profile version. - [ ] **Step 1: Write a failing manifest and mapping test** ```java @ParameterizedTest @MethodSource("commandCases") void mapsFrozenCommandCases(PlcCommandCase sample) { EncodedWrites writes = encoder.encode(sample.getCommand(), sample.getProfile()); assertEquals(sample.getExpectedWrites(), writes.asAddressValuePairs()); } @Test void refusesTwoStageCapabilityWhenItsCorpusIsMissingOrFails() { CapabilityGateResult result = gate.evaluate(twoStageProfile(), corpusWithoutPassingTwoStage()); assertFalse(result.isPublishable()); assertEquals("TWO_STAGE_PROTOCOL_GATE_FAILED", result.getReasonCode()); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-admin -am -Dtest=Ft546PlcCorpusRegressionTest test` - [ ] **Step 3: Freeze sanitized production samples** Export only bounded register values and logical facts; remove IPs, credentials and unrelated data. Calculate canonical UTF-8 hashes and reject corpus load when any hash or row count differs. - [ ] **Step 4: Run all Adapter contract tests GREEN** Run: `mvn -pl ruoyi-admin -am -Dtest='Ft546*AdapterTest,Ft546PlcCorpusRegressionTest' test` - [ ] **Step 5: Commit** ```bash git add ruoyi-admin/src/test/resources/ft546/plc/corpus ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/Ft546PlcCorpusRegressionTest.java git commit -m "test(ft546): freeze PLC protocol regression corpus" ``` ### Task 5: Add the real PLC and hardware-in-the-loop contract gate **Files:** - Modify: `ruoyi-admin/pom.xml` - Create: `ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/hil/Ft546PlcHilContractTest.java` - Create: `ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/hil/HilSafetyGuard.java` - Create: `ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/hil/HilContractReportWriter.java` - Create: `docs/wcs/acceptance/ft546-plc-hil-runbook.md` - Create at runtime: `outputs/wcs-acceptance/ft546-plc-hil-report.json` **Interfaces:** - Maven profile `ft546-plc-hil` excludes the suite unless `ft546.hil.enabled=true` and requires an isolated test PLC endpoint, expected profile version, device ID, safe start site, safe target site, and operator approval ID. - `HilSafetyGuard.verify()` performs read-only connection, AUTO/healthy/idle state, no load, no active command, expected start site, E-stop-chain-ready fact, and exclusive test lease checks before any write. - The suite always verifies snapshot mapping and point-to-point write/ack/final-fact semantics; it verifies the legacy two-stage path only when the published profile requests `TWO_STAGE_ATOMIC_TASK`. - [ ] **Step 1: Write failing guard and report tests with a fake PLC runtime** ```java @Test void refusesMotionWhenAnySafetyPreconditionIsMissing() { fakeRuntime.setEmergencyChainReady(false); assertThrows(HilSafetyException.class, () -> guard.verify()); assertEquals(0, fakeRuntime.writeCount()); } @Test void sanitizedReportContainsNoEndpointOrCredential() throws Exception { Path report = writer.write(passingHilResult()); String json = new String(Files.readAllBytes(report), StandardCharsets.UTF_8); assertFalse(json.contains("192.168.")); assertFalse(json.toLowerCase(Locale.ROOT).contains("password")); } ``` - [ ] **Step 2: Run RED on the fake runtime** Run: `mvn -pl ruoyi-admin -am -Dtest=Ft546PlcHilContractTest test` - [ ] **Step 3: Implement the guarded HIL profile** The test acquires an exclusive lease, records the initial snapshot, sends exactly one enabled command through `Ft546LegacySrgvAdapter`, waits for the standard completion predicate, returns the rig to its approved safe site, records the final snapshot, and releases the lease. Any timeout becomes `ACCEPTED_UNKNOWN`, stops further motion writes, records the isolated state, and requires the operator runbook before the suite can be rerun. - [ ] **Step 4: Run against the isolated FT546 test PLC** ```bash mvn -pl ruoyi-admin -Pft546-plc-hil \ -Dft546.hil.enabled=true \ -Dft546.hil.endpointRef=FT546_HIL_ENDPOINT \ -Dft546.hil.profileVersion=1 \ -Dft546.hil.deviceId=1 \ -Dft546.hil.startSite=HIL_SAFE_A \ -Dft546.hil.targetSite=HIL_SAFE_B \ -Dft546.hil.operatorApprovalId=${HIL_APPROVAL_ID} \ -Dtest=Ft546PlcHilContractTest test ``` Expected: all enabled command contracts pass, the device returns to the approved safe site, and `ft546-plc-hil-report.json` contains the profile hash, test cases, sanitized facts, result, Git commit, operator approval ID, and run time. - [ ] **Step 5: Commit the suite and runbook** ```bash git add ruoyi-admin/pom.xml ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/hil docs/wcs/acceptance/ft546-plc-hil-runbook.md git commit -m "test(ft546): add guarded PLC hardware contract gate" ``` ### Task 6: Verify restart, fault recovery, and manual takeover matrix **Files:** - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/RecoveryFaultMatrixTest.java` - Create: `ruoyi-wcs/src/test/resources/acceptance/fault-matrix.json` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/RecoveryMatrixReportWriter.java` **Interfaces:** - Matrix includes send-before failure, ACK loss, device offline, PLC restart, position mismatch, load mismatch, lift fault, conveyor mode conflict, WMS callback failure, WCS restart, duplicate restart, operator-confirmed resume, and operator-aborted task. - Produces one `RecoveryScenarioResult` per scenario with terminal state, isolation scope, manual requirement, released resources, duplicate command count, and duplicate callback count. - Produces `manual-recovery-audit.json` with operator ID, reason, submitted evidence, pre-state, post-state, affected resources, permission result, and timestamp for the two manual scenarios. - [ ] **Step 1: Write failing matrix-completeness test** ```java @Test void executesEveryRequiredRecoveryScenario() { RecoveryMatrixResult result = runner.run("acceptance/fault-matrix.json"); assertEquals(13, result.getScenarioCount()); assertEquals(0, result.getBlindRetries()); assertEquals(0, result.getUnsafeReleases()); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=RecoveryFaultMatrixTest test` - [ ] **Step 3: Implement restart checkpoints** For each scenario stop the runtime after the configured persisted checkpoint, discard all in-memory services, construct a new runtime with the same database snapshot and a new instance token, then run `StartupRecoveryService.recover()` before any dispatch. - [ ] **Step 4: Run GREEN and write report** Run: `mvn -pl ruoyi-wcs -am -Dtest=RecoveryFaultMatrixTest test` Expected reports: `outputs/wcs-acceptance/recovery-matrix.json` with 13 PASS rows and zero blind retry/unsafe release, plus `outputs/wcs-acceptance/manual-recovery-audit.json` with two fully audited manual actions. - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance ruoyi-wcs/src/test/resources/acceptance/fault-matrix.json git commit -m "test(wcs): verify recovery fault matrix" ``` ### Task 7: Add performance and long-run profiles **Files:** - Modify: `ruoyi-wcs/pom.xml` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/PerformanceBaselineTest.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/LongRunStabilityTest.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/AcceptanceMetrics.java` **Interfaces:** - Maven profile `wcs-performance` includes JUnit tag `performance`. - Maven profile `wcs-long-run` includes JUnit tag `long-run` and properties `wcs.longRun.hours`, `wcs.longRun.minimumTasks`, `wcs.seed`. - [ ] **Step 1: Write failing baseline assertions** ```java @Test @Tag("performance") void meetsTheApprovedStandardScale() { AcceptanceMetrics metrics = benchmark.run(scale(50, 10, 300, 20, 2000)); assertTrue(metrics.getDispatchP95Millis() <= 500); assertTrue(metrics.getEventCommitP95Millis() <= 200); assertTrue(metrics.getAutomaticRecoveryMillis() <= 60000); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -Pwcs-performance -Dtest=PerformanceBaselineTest test` - [ ] **Step 3: Implement measurement without weakening assertions** Warm up 1000 cycles, measure at least 10000 cycles, use `System.nanoTime()` only for elapsed time, publish P50/P95/P99 and throughput, and fail when task backlog grows for 100 consecutive cycles. - [ ] **Step 4: Add explicit long-run entry** ```java @Test @Tag("long-run") void completesTheConfiguredLongRunWithoutInvariantViolations() { AcceptanceMetrics metrics = longRun.run(hours(), minimumTasks(), seed()); assertTrue(metrics.getCompletedTasks() >= minimumTasks()); assertEquals(0, metrics.getSafetyViolations()); assertEquals(0, metrics.getDuplicateBusinessEffects()); } ``` - [ ] **Step 5: Run short CI and full acceptance modes** CI smoke: `mvn -pl ruoyi-wcs -Pwcs-long-run -Dwcs.longRun.hours=0.05 -Dwcs.longRun.minimumTasks=1000 -Dwcs.seed=20260804 test` Full acceptance: `mvn -pl ruoyi-wcs -Pwcs-long-run -Dwcs.longRun.hours=72 -Dwcs.longRun.minimumTasks=100000 -Dwcs.seed=20260804 test` - [ ] **Step 6: Commit** ```bash git add ruoyi-wcs/pom.xml ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance git commit -m "test(wcs): add performance and long run acceptance" ``` ### Task 8: Generate the final acceptance evidence bundle **Files:** - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/AcceptanceReportWriter.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance/AcceptanceReportWriterTest.java` - Create: `docs/wcs/acceptance/README.md` - Create: `docs/wcs/acceptance/standard-wcs-acceptance-index.md` - Create at runtime: `outputs/wcs-standard-acceptance-{runId}/summary.json` - Create at runtime: `outputs/wcs-standard-acceptance-{runId}/report.md` - Create at runtime: `outputs/wcs-standard-acceptance-{runId}/manifest.sha256` **Interfaces:** - `summary.json` contains Git commit, core JAR hash, FT546 config hash, Warehouse B config hash, test counts, replay counts, performance metrics, recovery matrix, safety counts, start/end time, and verdict. - `report.md` contains dedicated sections for structured-log field coverage, required metric coverage, manual takeover audit, PLC corpus capability evidence, and the latest guarded HIL report. - Manifest covers every file except itself. - [ ] **Step 1: Write a failing evidence-completeness test** ```java @Test void writesACompleteTamperEvidentEvidenceBundle() throws Exception { Path output = writer.write(passingAcceptanceResult()); assertTrue(Files.exists(output.resolve("summary.json"))); assertTrue(Files.exists(output.resolve("report.md"))); assertTrue(Files.exists(output.resolve("manifest.sha256"))); assertTrue(manifestVerifier.verify(output)); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=AcceptanceReportWriterTest test` - [ ] **Step 3: Implement canonical reports and verdict rules** Verdict is PASS only when both warehouse configurations use the same core JAR hash, all required tests ran, historical replay has no safety violations, fault/manual matrix passes, required logs and metrics are observed, PLC capability evidence matches the published profile, the guarded HIL report passes every enabled FT546 command, performance thresholds pass, long-run minimum task count is reached, and every absolute safety count is zero. - [ ] **Step 4: Run the complete standard verification** ```bash mvn -pl ruoyi-wcs,ruoyi-admin -am test mvn -pl ruoyi-wcs -Pwcs-performance test cd javaTest && mvn clean test cd ../ruoyi-ui && npm run build:prod ``` Then run the approved full long-run command and generate the evidence bundle. - [ ] **Step 5: Verify repository and evidence integrity** Run: `git diff --check` Run the manifest verifier against the generated directory. Expected: all hashes match and the acceptance index links the exact run ID and Git commit. - [ ] **Step 6: Commit** ```bash git add ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance docs/wcs/acceptance git commit -m "docs(wcs): record standard product acceptance" ```