466 lines
21 KiB
Markdown
466 lines
21 KiB
Markdown
# 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"
|
||
```
|