21 KiB
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 injectsDISCONNECT,COMMAND_ACK_LOST,DUPLICATE_EVENT,OUT_OF_ORDER_EVENT,COMMAND_REJECTED,POSITION_MISMATCH,LOAD_MISMATCH,MANUAL_MODE, orWCS_RESTART. -
SimulationResultexposes 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
@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
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, andChargingPolicyports. -
Step 1: Write a failing no-core-change acceptance test
@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
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.9tojavaTest. -
Consumes existing
HistoricalQueueLoaderand frozen manifest. -
Maps each record to canonical
CreateTransportOrderCommand; historicalagv_id/path_info/finish_dateremain comparison metadata only. -
Step 1: Install the core and write a failing replay test
Run: mvn -pl ruoyi-wcs -am install -DskipTests
Add:
@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
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
Ft546CapabilityEvidenceonly when every required point-to-point case passes and, whenTWO_STAGE_ATOMIC_TASKis requested, every two-stage case passes for the exact profile version. -
Step 1: Write a failing manifest and mapping test
@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
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-hilexcludes the suite unlessft546.hil.enabled=trueand 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
@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
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
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
RecoveryScenarioResultper scenario with terminal state, isolation scope, manual requirement, released resources, duplicate command count, and duplicate callback count. -
Produces
manual-recovery-audit.jsonwith 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
@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
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-performanceincludes JUnit tagperformance. -
Maven profile
wcs-long-runincludes JUnit taglong-runand propertieswcs.longRun.hours,wcs.longRun.minimumTasks,wcs.seed. -
Step 1: Write failing baseline assertions
@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
@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
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.jsoncontains 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.mdcontains 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
@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
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
git add ruoyi-wcs/src/test/java/com/ruoyi/wcs/acceptance docs/wcs/acceptance
git commit -m "docs(wcs): record standard product acceptance"