# Standard WCS Core Foundation 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:** 建立可被 `ruoyi-admin` 依赖的 `ruoyi-wcs` 模块,并交付订单、流程、步骤、幂等和核心持久化边界。
**Architecture:** `ruoyi-wcs` 只暴露入站用例、出站端口和查询 DTO;任务状态迁移保存在领域对象中,应用服务负责事务和幂等。Admin 只增加模块依赖,本计划不接 PLC,也不替换现有调度线程。
**Tech Stack:** Java 8, Spring Boot 2.5.15, Maven, JUnit 5, ArchUnit 1.2.1, MyBatis, SQL Server.
## Global Constraints
- 根 Reactor 新增且只新增 `ruoyi-wcs` 产品模块。
- `ruoyi-wcs` 只依赖 `ruoyi-common`、Spring 事务、MyBatis 和测试依赖;不依赖 `ruoyi-admin`、PLC 库或项目包。
- 所有公共接口使用 Java 8 类型,不使用 record、sealed class 或高版本 API。
- 订单、步骤和事件 ID 使用非空 `String`,时间使用 `java.time.Instant`。
- 外部请求以 `externalRequestId` 幂等;重复请求返回同一 `orderId`。
- 终态订单和步骤不可反向迁移。
- 本计划不修改 `BaseSRgvThread`、`PlcSrgvRead`、现有 WMS Controller 或旧业务表。
---
### Task 1: Add the ruoyi-wcs Maven module and architecture test harness
**Files:**
- Modify: `pom.xml`
- Modify: `ruoyi-admin/pom.xml`
- Create: `ruoyi-wcs/pom.xml`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/WcsModule.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/architecture/WcsModuleArchitectureTest.java`
**Interfaces:**
- Produces: Maven artifact `com.ruoyi:ruoyi-wcs:${ruoyi.version}`.
- Produces: marker type `com.ruoyi.wcs.WcsModule`.
- Enforces: no class under `com.ruoyi.wcs..` depends on `com.ruoyi.schedule..`, `com.ruoyi.api..`, `HslCommunication..`, or `org.apache.plc4x..`.
- [ ] **Step 1: Add the module skeleton and a deliberately failing boundary test**
Add `ruoyi-wcs` after `ruoyi-common`, add the managed dependency, add the Admin dependency, and create this test before `WcsModule` exists:
```java
@Test
void exposesAStableModuleMarker() throws Exception {
assertEquals("com.ruoyi.wcs.WcsModule",
Class.forName("com.ruoyi.wcs.WcsModule").getName());
}
```
Configure `spring-boot-starter-test` and `archunit-junit5:1.2.1` as test dependencies in `ruoyi-wcs/pom.xml`; configure Surefire `3.2.5` in the root build.
- [ ] **Step 2: Run the module test and observe RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=WcsModuleArchitectureTest test`
Expected: FAIL because `com.ruoyi.wcs.WcsModule` does not exist.
- [ ] **Step 3: Add the marker and dependency rules**
```java
package com.ruoyi.wcs;
public final class WcsModule {
private WcsModule() {
}
}
```
Add ArchUnit rules:
```java
@Test
void coreDoesNotDependOnProjectOrPlcPackages() {
noClasses().that().resideInAPackage("com.ruoyi.wcs..")
.should().dependOnClassesThat()
.resideInAnyPackage("com.ruoyi.schedule..", "com.ruoyi.api..",
"HslCommunication..", "org.apache.plc4x..")
.check(new ClassFileImporter().importPackages("com.ruoyi.wcs"));
}
```
- [ ] **Step 4: Run module and Admin reactor tests GREEN**
Run: `mvn -pl ruoyi-wcs,ruoyi-admin -am test`
Expected: Reactor succeeds and Surefire reports at least one `ruoyi-wcs` test.
- [ ] **Step 5: Commit**
```bash
git add pom.xml ruoyi-admin/pom.xml ruoyi-wcs
git commit -m "build: add standard WCS core module"
```
### Task 2: Define shared time, ID, event, and failure contracts
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared/WcsClock.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared/IdGenerator.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared/DomainEvent.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared/DomainRuleViolation.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/shared/SharedContractsTest.java`
**Interfaces:**
- Produces: `WcsClock.now(): Instant`.
- Produces: `IdGenerator.nextId(): String`.
- Produces: `DomainEvent.getEventId/getAggregateId/getSequence/getOccurredAt/getType`.
- [ ] **Step 1: Write failing immutable event tests**
```java
@Test
void rejectsBlankEventIdentity() {
assertThrows(IllegalArgumentException.class, () ->
new DomainEvent("", "ORDER-1", 1L, Instant.EPOCH, "OrderAccepted"));
}
@Test
void exposesDeterministicEventFields() {
DomainEvent event = new DomainEvent("E-1", "ORDER-1", 2L,
Instant.parse("2026-08-04T00:00:00Z"), "OrderPlanned");
assertEquals(2L, event.getSequence());
assertEquals("OrderPlanned", event.getType());
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=SharedContractsTest test`
Expected: compilation fails because the shared contracts do not exist.
- [ ] **Step 3: Implement the minimal contracts**
```java
public interface WcsClock {
Instant now();
}
public interface IdGenerator {
String nextId();
}
public final class DomainRuleViolation extends RuntimeException {
public DomainRuleViolation(String message) {
super(message);
}
}
```
Implement `DomainEvent` as a final immutable class with constructor validation and getters.
- [ ] **Step 4: Run GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest=SharedContractsTest test`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/shared ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/shared
git commit -m "feat(wcs): add shared domain contracts"
```
### Task 3: Implement the TransportOrder state machine
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TransportOrderState.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TransportOrderType.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TransportOrder.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/task/TransportOrderTest.java`
**Interfaces:**
- Produces: `TransportOrder.accept(...)` factory.
- Produces: `plan(String workflowId)`, `reject(String code,String reason)`, `start()`, `suspend(String reason)`, `resumeToPlanned()`, `resumeToExecuting()`, `requestCancel()`, `cancel()`, `complete()`, `fail(String reason)`.
- Produces: `getUncommittedEvents(): List` and `clearUncommittedEvents()`.
- [ ] **Step 1: Write failing legal and illegal transition tests**
```java
@Test
void completesOnlyThroughPlannedAndExecuting() {
TransportOrder order = order();
order.plan("WF-1");
order.start();
order.complete();
assertEquals(TransportOrderState.COMPLETED, order.getState());
}
@Test
void terminalOrderCannotResume() {
TransportOrder order = order();
order.plan("WF-1");
order.cancel();
assertThrows(DomainRuleViolation.class, order::resumeToPlanned);
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderTest test`
- [ ] **Step 3: Implement explicit transition guards**
```java
public void start() {
requireState(TransportOrderState.PLANNED);
transitionTo(TransportOrderState.EXECUTING, "OrderStarted");
}
public void requestCancel() {
if (state != TransportOrderState.EXECUTING
&& state != TransportOrderState.SUSPENDED) {
throw new DomainRuleViolation("only executing or suspended order can request cancellation");
}
transitionTo(TransportOrderState.CANCEL_REQUESTED, "OrderCancelRequested");
}
```
Implement this exact transition set and increment event sequence exactly once per successful transition:
- `ACCEPTED -> PLANNED/REJECTED/SUSPENDED/CANCELLED/FAILED`.
- `PLANNED -> EXECUTING/SUSPENDED/CANCELLED/FAILED`.
- `EXECUTING -> COMPLETED/SUSPENDED/CANCEL_REQUESTED/FAILED`.
- `SUSPENDED -> PLANNED/EXECUTING/CANCEL_REQUESTED/FAILED`.
- `CANCEL_REQUESTED -> CANCELLED/FAILED` only after application-level command and resource convergence checks.
- `REJECTED/COMPLETED/CANCELLED/FAILED` are mutually exclusive terminal states and reject every further transition.
- [ ] **Step 4: Run state tests GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderTest test`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/task
git commit -m "feat(wcs): add transport order state machine"
```
### Task 4: Implement WorkflowInstance and TaskStep dependencies
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TaskStepState.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TaskStepType.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/TaskStep.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/WorkflowInstance.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/WorkflowDefinition.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task/WorkflowStepDefinition.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WorkflowDefinitionProvider.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/task/WorkflowInstanceTest.java`
**Interfaces:**
- Produces: `WorkflowDefinitionProvider.definitionFor(TransportOrderType): WorkflowDefinition`.
- Produces: `WorkflowInstance.create(String workflowId, String orderId, WorkflowDefinition)`.
- Produces: `readySteps(): List` and `onStepSucceeded(String stepId)`.
- `TaskStep` exposes `markReady`, `block(reasonCode)`, `unblock`, `assign(vehicleId,reservationIds)`, `start`, `suspend(reason)`, `resumeAssigned`, `resumeRunning`, `succeed`, `skip`, `cancel`, and `fail(reason)`.
- [ ] **Step 1: Write failing dependency and optional-step tests**
```java
@Test
void exposesOnlyStepsWhoseMandatoryDependenciesSucceeded() {
WorkflowInstance workflow = workflow("MOVE", "DROP");
assertEquals(Collections.singletonList("MOVE"), ids(workflow.readySteps()));
workflow.step("MOVE").assign("VEH-1");
workflow.step("MOVE").start();
workflow.step("MOVE").succeed();
workflow.onStepSucceeded("MOVE");
assertEquals(Collections.singletonList("DROP"), ids(workflow.readySteps()));
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=WorkflowInstanceTest test`
- [ ] **Step 3: Implement dependency evaluation and transition methods**
```java
public List readySteps() {
List result = new ArrayList();
for (TaskStep step : steps.values()) {
if (step.isWaiting() && mandatoryDependenciesSucceeded(step)) {
step.markReady();
result.add(step);
}
}
return Collections.unmodifiableList(result);
}
```
Implement mandatory dependency failure as `FAILED`, optional branch exclusion as `SKIPPED`, and these exact transitions: `WAITING -> READY/BLOCKED/SKIPPED/CANCELLED/FAILED`; `BLOCKED -> READY/CANCELLED/FAILED`; `READY -> ASSIGNED/BLOCKED/CANCELLED/FAILED`; `ASSIGNED -> RUNNING/SUSPENDED/CANCELLED/FAILED`; `RUNNING -> SUCCEEDED/SUSPENDED/CANCELLED/FAILED`; `SUSPENDED -> ASSIGNED/RUNNING/CANCELLED/FAILED`. `SKIPPED/CANCELLED/FAILED/SUCCEEDED` are terminal.
- [ ] **Step 4: Run GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest=WorkflowInstanceTest,TransportOrderTest test`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/task ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WorkflowDefinitionProvider.java ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/task
git commit -m "feat(wcs): add workflow and task step model"
```
### Task 5: Add standard order use cases and idempotent application service
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/in/TransportOrderUseCase.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/task/CreateTransportOrderCommand.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/task/TransportOrderResult.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/task/TransportOrderApplicationService.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/TransportOrderRepository.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/DomainEventPublisher.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WmsNotificationGateway.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WmsNotification.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/WmsNotificationReceipt.java`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/application/task/TransportOrderApplicationServiceTest.java`
**Interfaces:**
- Produces: `create(CreateTransportOrderCommand)`, `cancel(String externalRequestId)`, `suspend(String orderId,String reason)`, `resume(String orderId)`.
- Consumes: `TransportOrderRepository.findByExternalRequestId/save`.
- Consumes: `WorkflowDefinitionProvider` and `DomainEventPublisher.publish(List)`.
- Produces outbound port `WmsNotificationGateway.notify(WmsNotification): WmsNotificationReceipt`; later plans deliver it through a core-owned Outbox.
- `WmsNotification` contains `eventId`, `externalRequestId`, `orderId`, `occurredAt`, `eventType`, `resultCode`, `resultMessage`, and typed context fields for `OrderAccepted`, `OrderStarted`, `OrderCompleted`, `OrderFailed`, `OrderCancelled`, and `OrderSuspended`.
- [ ] **Step 1: Write failing duplicate-request test**
```java
@Test
void returnsTheExistingOrderForTheSameExternalRequest() {
TransportOrderResult first = service.create(command("REQ-1"));
TransportOrderResult second = service.create(command("REQ-1"));
assertEquals(first.getOrderId(), second.getOrderId());
assertEquals(1, repository.count());
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderApplicationServiceTest test`
- [ ] **Step 3: Implement transactional orchestration**
```java
@Transactional
public TransportOrderResult create(CreateTransportOrderCommand command) {
TransportOrder existing = repository.findByExternalRequestId(command.getExternalRequestId());
if (existing != null) {
return TransportOrderResult.from(existing);
}
TransportOrder order = TransportOrder.accept(command, idGenerator.nextId(), clock.now());
WorkflowDefinition definition = workflowDefinitions.definitionFor(order.getType());
WorkflowInstance workflow = WorkflowInstance.create(idGenerator.nextId(), order.getOrderId(), definition);
order.plan(workflow.getWorkflowId());
repository.save(order, workflow);
eventPublisher.publish(order.getUncommittedEvents());
order.clearUncommittedEvents();
return TransportOrderResult.from(order);
}
```
Implement cancel/suspend/resume using domain methods; do not expose repository entities in results.
Map order domain events to immutable `WmsNotification` values without calling the gateway in the order transaction. `WmsNotificationReceipt` exposes `DELIVERED`, `RETRYABLE_FAILURE`, and `PERMANENT_FAILURE`; the notification `eventId` is always the external idempotency key.
- [ ] **Step 4: Run GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest='TransportOrder*Test,WorkflowInstanceTest' test`
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/task ruoyi-wcs/src/main/java/com/ruoyi/wcs/port ruoyi-wcs/src/test/java/com/ruoyi/wcs/application/task
git commit -m "feat(wcs): add idempotent transport order use cases"
```
### Task 6: Add the foundation SQL schema and MyBatis repository
**Files:**
- Create: `sql/wcs/V001__wcs_task_foundation.sql`
- Create: `sql/wcs/verify_V001__wcs_task_foundation.sql`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/TransportOrderRow.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/WorkflowRow.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/TaskStepRow.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/TransportOrderMapper.java`
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task/MyBatisTransportOrderRepository.java`
- Create: `ruoyi-wcs/src/main/resources/mapper/wcs/task/TransportOrderMapper.xml`
- Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/infrastructure/persistence/task/TransportOrderRowMapperTest.java`
**Interfaces:**
- Implements: `TransportOrderRepository` from Task 5.
- Produces: unique constraint `uk_wcs_transport_order_external_request`.
- Produces: optimistic `state_version` on order, workflow, and step rows.
- [ ] **Step 1: Write a failing domain-row round-trip test**
```java
@Test
void preservesOrderStateAndVersionAcrossPersistenceMapping() {
TransportOrder source = executingOrder("ORDER-1", 4L);
TransportOrderRow row = TransportOrderRow.fromDomain(source);
TransportOrder restored = row.toDomain();
assertEquals(source.getState(), restored.getState());
assertEquals(4L, restored.getStateVersion());
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderRowMapperTest test`
- [ ] **Step 3: Create idempotent SQL Server DDL**
The first table must include this exact invariant:
```sql
IF OBJECT_ID('dbo.wcs_transport_order', 'U') IS NULL
BEGIN
CREATE TABLE dbo.wcs_transport_order (
order_id varchar(64) NOT NULL PRIMARY KEY,
external_request_id varchar(128) NOT NULL,
order_type varchar(32) NOT NULL,
load_id varchar(128) NULL,
source_code varchar(128) NOT NULL,
destination_code varchar(128) NOT NULL,
priority int NOT NULL,
order_state varchar(32) NOT NULL,
workflow_id varchar(64) NULL,
state_version bigint NOT NULL,
created_time datetime2(3) NOT NULL,
updated_time datetime2(3) NOT NULL,
CONSTRAINT uk_wcs_transport_order_external_request UNIQUE (external_request_id)
);
END;
```
Add `wcs_workflow_instance`, `wcs_task_step`, `wcs_task_dependency`, and `wcs_domain_event` with named constraints and no `default02-default08` columns.
- [ ] **Step 4: Implement mapping and optimistic updates**
`TransportOrderMapper.updateState` must include `WHERE order_id = #{orderId} AND state_version = #{expectedVersion}` and increment `state_version`; repository throws `OptimisticLockingFailureException` when update count is 0.
- [ ] **Step 5: Run mapping and schema text tests GREEN**
Run: `mvn -pl ruoyi-wcs -am -Dtest=TransportOrderRowMapperTest test`
Run the read-only verification script against the isolated WCS test database and expect one row per required table and unique constraint.
- [ ] **Step 6: Commit**
```bash
git add sql/wcs ruoyi-wcs/src/main/java/com/ruoyi/wcs/infrastructure/persistence/task ruoyi-wcs/src/main/resources/mapper/wcs/task ruoyi-wcs/src/test/java/com/ruoyi/wcs/infrastructure/persistence/task
git commit -m "feat(wcs): persist transport orders and workflows"
```
### Task 7: Wire the core module into RuoYi without changing legacy behavior
**Files:**
- Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/WcsCoreConfiguration.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/wcs/WcsCoreWiringTest.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/wcs/TestPersistenceConfiguration.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/wcs/AdminWcsBoundaryArchitectureTest.java`
- Verify: `ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java`
- Verify: `ruoyi-admin/src/main/resources/application.yml`
**Interfaces:**
- Produces: Spring beans `TransportOrderUseCase`, `TransportOrderRepository`, `WcsClock`, and `IdGenerator`.
- Does not change: `ScheduleThread`, `BaseSRgvThread`, `Wcs2WmsController`, or PLC writes.
- Enforces: project/Admin packages may use `com.ruoyi.wcs.port.in..`, `com.ruoyi.wcs.port.out..`, and `com.ruoyi.wcs.query..`, but may not depend on `com.ruoyi.wcs.infrastructure.persistence..` or concrete Repository implementations.
- [ ] **Step 1: Write a failing Spring wiring test**
```java
@Test
void exposesTheTransportOrderUseCaseFromTheWcsModule() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(WcsCoreConfiguration.class, TestPersistenceConfiguration.class);
context.refresh();
assertNotNull(context.getBean(TransportOrderUseCase.class));
}
}
```
Create the test-only dependency configuration explicitly:
```java
@TestConfiguration
public class TestPersistenceConfiguration {
@Bean
public TransportOrderRepository transportOrderRepository() {
return Mockito.mock(TransportOrderRepository.class);
}
@Bean
public WorkflowDefinitionProvider workflowDefinitionProvider() {
return Mockito.mock(WorkflowDefinitionProvider.class);
}
@Bean
public DomainEventPublisher domainEventPublisher() {
return Mockito.mock(DomainEventPublisher.class);
}
}
```
- [ ] **Step 2: Run RED**
Run: `mvn -pl ruoyi-admin -am -Dtest=WcsCoreWiringTest test`
- [ ] **Step 3: Add explicit core configuration**
```java
@Configuration
@ComponentScan(basePackages = "com.ruoyi.wcs")
public class WcsCoreConfiguration {
}
```
Do not alter `@MapperScan("com.ruoyi.**.mapper")`; add a test assertion that `TransportOrderMapper` is found under the existing wildcard scan.
Add an ArchUnit rule importing `com.ruoyi.project..` and `com.ruoyi.api..` that fails on dependencies to `com.ruoyi.wcs.infrastructure.persistence..`; allow only the public ports, application use cases, domain value types required by those ports, and query DTOs.
- [ ] **Step 4: Run the full foundation verification**
Run: `mvn -pl ruoyi-wcs,ruoyi-admin -am test`
Run: `git diff --check`
Expected: tests pass, no PLC or scheduler source file is modified, and the module boundary test passes.
- [ ] **Step 5: Commit**
```bash
git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/WcsCoreConfiguration.java ruoyi-admin/src/test/java/com/ruoyi/wcs
git commit -m "feat(wcs): wire core module into RuoYi"
```