# Standard WCS Topology Dispatch Traffic 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:** 在标准订单与步骤模型之上实现版本化仓库拓扑、设备能力、选车、路径规划、资源预约和无环交通裁决。 **Architecture:** 运行时只读取编译后的不可变 `EffectiveTopology`;车辆选择和路径规划产生候选,`ResourceLedger` 与 `DeadlockGuard` 在同一调度快照中决定 GRANT/HOLD/YIELD。项目只提供定义和评分权重,不直接持有资源或修改步骤状态。 **Tech Stack:** Java 8, JUnit 5, MyBatis, SQL Server, deterministic graph search. ## Global Constraints - 依赖 `2026-08-04-standard-wcs-core-foundation.md` 已交付的 `TransportOrder`、`TaskStep`、共享事件和 Repository 边界。 - 交通区域是版本与协调边界,不默认是容量 1 的资源。 - 节点和无向边默认容量为 1;容量大于 1 必须显式配置。 - 地图加载后不可变;运行循环不得临时查询多张地图表或解释 `remark`。 - 车辆筛选先执行硬条件,再执行可配置评分。 - 已占资源只有匹配物理事实或恢复裁定才能释放。 - 等待图新增授权后必须无环。 - 本计划只生成设备无关的调度决定,不调用 PLC。 --- ### Task 1: Define, validate, publish, and compile versioned warehouse configuration **Files:** - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/topology/TopologyDefinition.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/topology/TopologyNode.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/topology/TopologyEdge.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/topology/ResourceDefinition.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/topology/EffectiveTopology.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/topology/TopologyCompiler.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/in/ConfigurationUseCase.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/TopologyDefinitionProvider.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/ConfigurationRepository.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/configuration/PublishConfigurationCommand.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/configuration/ConfigurationPublicationResult.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/configuration/PublishedConfigurationView.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/configuration/ConfigurationApplicationService.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/topology/TopologyCompilerTest.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/application/configuration/ConfigurationApplicationServiceTest.java` **Interfaces:** - Consumes: `TopologyDefinitionProvider.load(String warehouseCode, long version)`. - Produces: `TopologyCompiler.compile(TopologyDefinition): EffectiveTopology`. - Produces: `EffectiveTopology.neighbors(String nodeId)`, `edge(String sourceNodeId,String targetNodeId)`, `resource(String resourceKey)`, `containsNode(String nodeId)`, `version()`. - Produces: `ConfigurationUseCase.validate(PublishConfigurationCommand)`, `publish(PublishConfigurationCommand): ConfigurationPublicationResult`, `read(String warehouseCode,long version): PublishedConfigurationView`, and `readActive(String warehouseCode): PublishedConfigurationView`. - `publish` atomically stores the immutable warehouse, floor, topology, traffic-zone, resource, device, capability, port, charger, and workflow version after all cross-reference checks pass. - Rejects publication while an active order references the version being replaced; running orders remain pinned to their original topology/configuration version. - [ ] **Step 1: Write failing topology invariant tests** ```java @Test void canonicalizesUndirectedEdgeResources() { EffectiveTopology topology = compiler.compile(definition( node("A", 1), node("B", 1), edge("A", "B"))); assertEquals("EDGE:A:B", topology.edge("A", "B").getResourceKey()); assertEquals("EDGE:A:B", topology.edge("B", "A").getResourceKey()); } @Test void rejectsDisconnectedLiftPortReference() { assertThrows(DomainRuleViolation.class, () -> compiler.compile(definitionWithLiftPort("MISSING"))); } @Test void publishesOneImmutableConfigurationVersionAtomically() { ConfigurationPublicationResult result = service.publish(validConfiguration(7L)); assertEquals(7L, result.getPublishedVersion()); assertTrue(repository.isPublished("FT546", 7L)); assertEquals(0, repository.partialWriteCount()); } @Test void readsOneInternallyConsistentPublishedVersion() { service.publish(validConfiguration(7L)); PublishedConfigurationView view = service.read("FT546", 7L); assertEquals(7L, view.getTopologyVersion()); assertEquals(7L, view.getDeviceConfigurationVersion()); assertEquals(7L, view.getWorkflowConfigurationVersion()); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=TopologyCompilerTest,ConfigurationApplicationServiceTest test` - [ ] **Step 3: Implement immutable compilation** ```java public EffectiveTopology compile(TopologyDefinition definition) { requirePublishedVersion(definition); Map nodes = uniqueNodes(definition.getNodes()); Map edges = canonicalEdges(definition.getEdges(), nodes); validatePortsAndResources(definition, nodes); return new EffectiveTopology(definition.getWarehouseCode(), definition.getVersion(), nodes, edges, immutableResources(definition, nodes, edges)); } ``` Reject duplicate IDs, duplicate coordinates on one floor, missing endpoints, nonpositive capacity, unconfirmed traffic zones, and lift/conveyor ports without nodes. `ConfigurationApplicationService.publish` first compiles topology, validates every device floor/capability/port/charger reference and every workflow step type, then calls one optimistic `ConfigurationRepository.publish(command, effectiveTopology)` transaction. It returns stable validation codes and never exposes partially published versions. `read` and `readActive` return immutable DTOs through the inbound use case. Admin configuration pages may call these methods but may not query the core configuration Mapper or tables directly. - [ ] **Step 4: Run GREEN** Run: `mvn -pl ruoyi-wcs -am -Dtest=TopologyCompilerTest,ConfigurationApplicationServiceTest test` - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/topology ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/configuration ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/in/ConfigurationUseCase.java ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/TopologyDefinitionProvider.java ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/ConfigurationRepository.java ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/topology ruoyi-wcs/src/test/java/com/ruoyi/wcs/application/configuration git commit -m "feat(wcs): publish immutable warehouse configuration" ``` ### Task 2: Add device registry and capability matching **Files:** - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/equipment/DeviceType.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/equipment/DeviceCapability.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/equipment/DeviceDescriptor.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/equipment/VehicleSnapshot.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/DeviceRegistry.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/equipment/DeviceCapabilityTest.java` **Interfaces:** - Produces: `DeviceRegistry.vehicles(): List`. - Produces: `DeviceDescriptor.supports(DeviceCapability): boolean`. - Produces: `VehicleSnapshot.isDispatchable(int minimumBattery)`. - [ ] **Step 1: Write failing dispatchability tests** ```java @ParameterizedTest @CsvSource({ "ONLINE,AUTO,NORMAL,IDLE,80,true", "OFFLINE,AUTO,NORMAL,IDLE,80,false", "ONLINE,MANUAL,NORMAL,IDLE,80,false", "ONLINE,AUTO,FAULT,IDLE,80,false", "ONLINE,AUTO,NORMAL,BUSY,80,false", "ONLINE,AUTO,NORMAL,IDLE,20,false" }) void dispatchabilityUsesOrthogonalStateDimensions( String connection, String mode, String health, String occupation, int battery, boolean expected) { assertEquals(expected, snapshot(connection, mode, health, occupation, battery) .isDispatchable(30)); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=DeviceCapabilityTest test` - [ ] **Step 3: Implement descriptors and snapshots** Keep connection, mode, health, occupation, location, load, battery, current command, floor, and snapshot time as separate immutable fields. `supports` checks an immutable `EnumSet`. - [ ] **Step 4: Run GREEN** Run: `mvn -pl ruoyi-wcs -am -Dtest=DeviceCapabilityTest test` - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/equipment ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/DeviceRegistry.java ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/equipment git commit -m "feat(wcs): add device capabilities and snapshots" ``` ### Task 3: Implement deterministic route planning **Files:** - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/RoutePlan.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/RoutePlanner.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/AStarRoutePlanner.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/traffic/AStarRoutePlannerTest.java` **Interfaces:** - Produces: `RoutePlanner.plan(EffectiveTopology,String source,String destination,Set blockedResources): RoutePlan`. - Produces: ordered node IDs, ordered resource keys, distance cost, turn count, and topology version. - [ ] **Step 1: Write failing shortest-safe-route tests** ```java @Test void avoidsBlockedEdgeAndUsesStableNodeIdTieBreak() { RoutePlan route = planner.plan(grid(), "A", "D", Collections.singleton("EDGE:B:D")); assertEquals(Arrays.asList("A", "C", "D"), route.getNodeIds()); } @Test void rejectsUnknownDestination() { assertThrows(DomainRuleViolation.class, () -> planner.plan(grid(), "A", "UNKNOWN", Collections.emptySet())); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=AStarRoutePlannerTest test` - [ ] **Step 3: Implement A* with deterministic ordering** Use Manhattan distance only when coordinates exist; otherwise use zero heuristic. Sort equal-cost open nodes by total cost, path cost, turn count, then node ID. Never read historical `path_info`. - [ ] **Step 4: Run GREEN** Run: `mvn -pl ruoyi-wcs -am -Dtest=AStarRoutePlannerTest,TopologyCompilerTest test` - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/traffic git commit -m "feat(wcs): add deterministic route planning" ``` ### Task 4: Implement vehicle assignment policy **Files:** - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/dispatch/DispatchTask.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/dispatch/VehicleAssignmentDecision.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/dispatch/VehicleAssignmentPolicy.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/dispatch/DefaultVehicleAssignmentPolicy.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/DispatchPolicy.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/dispatch/DefaultVehicleAssignmentPolicyTest.java` **Interfaces:** - Produces: `assign(DispatchTask,List,EffectiveTopology): VehicleAssignmentDecision`. - Produces: selected vehicle ID, route, total score, and ordered reason codes. - Consumes: optional `DispatchPolicy.adjustment(DispatchTask,VehicleSnapshot): int` with no side effects. - [ ] **Step 1: Write failing hard-filter and stable-score tests** ```java @Test void rejectsCloserVehicleWithoutRequiredCapability() { VehicleAssignmentDecision decision = policy.assign(taskRequiringLoad(), Arrays.asList(vehicle("V1", 1, 90, false), vehicle("V2", 2, 70, true)), topology); assertEquals("V2", decision.getVehicleId()); assertTrue(decision.getReasonCodes().contains("CAPABILITY_MATCH")); } @Test void breaksEqualScoresByVehicleId() { assertEquals("V1", policy.assign(task(), Arrays.asList(vehicle("V2"), vehicle("V1")), topology) .getVehicleId()); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=DefaultVehicleAssignmentPolicyTest test` - [ ] **Step 3: Implement filtering and scoring** Apply online/auto/healthy/idle/battery/capability/reachability filters before scoring. Score business priority, waiting age, same floor, path cost, battery margin, current workload, policy adjustment, then stable vehicle ID. - [ ] **Step 4: Run GREEN** Run: `mvn -pl ruoyi-wcs -am -Dtest='*VehicleAssignment*Test,*RoutePlannerTest' test` - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/dispatch ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/DispatchPolicy.java ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/dispatch git commit -m "feat(wcs): add deterministic vehicle assignment" ``` ### Task 5: Implement the resource ledger and reservation lifecycle **Files:** - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/ReservationState.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/ResourceReservation.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/ReservationRequest.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/ReservationDecision.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/ResourceLedger.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/traffic/ResourceLedgerTest.java` **Interfaces:** - Produces: `reserve(ReservationRequest): ReservationDecision`. - Produces: `occupy(String reservationId,long fencingToken)`. - Produces: `release(String reservationId,String releaseEvidenceId,long fencingToken)`. - Produces: `abortUnoccupied(String reservationId,String reason)`. - `ReservationState` values are `REQUESTED`, `GRANTED`, `OCCUPIED`, `RELEASED`, `REJECTED`, `EXPIRED`, and `ABORTED`. - [ ] **Step 1: Write failing capacity and UNKNOWN tests** ```java @Test void grantsOnlyOneReservationForCapacityOneResource() { assertTrue(ledger.reserve(request("R1", "NODE:A")).isGranted()); assertFalse(ledger.reserve(request("R2", "NODE:A")).isGranted()); } @Test void occupiedReservationCannotExpireByTime() { ResourceReservation reservation = occupiedReservation(); assertThrows(DomainRuleViolation.class, reservation::expire); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=ResourceLedgerTest test` - [ ] **Step 3: Implement atomic in-memory ledger semantics** Sort requested resource keys lexicographically, verify all capacities before changing any count, assign one fencing token to the decision, and roll back the whole request on any conflict. Permit only `REQUESTED -> GRANTED/REJECTED/EXPIRED/ABORTED`, `GRANTED -> OCCUPIED/EXPIRED/ABORTED`, and `OCCUPIED -> RELEASED`. `EXPIRED` is legal only before physical occupation; `OCCUPIED` can be released only with matching evidence/fencing token and must remain protected when a linked command is `UNKNOWN` or a RecoveryCase is unresolved. - [ ] **Step 4: Run GREEN** Run: `mvn -pl ruoyi-wcs -am -Dtest=ResourceLedgerTest test` - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/traffic/ResourceLedgerTest.java git commit -m "feat(wcs): add resource reservation ledger" ``` ### Task 6: Add deadlock prevention, HOLD, and YIELD decisions **Files:** - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/WaitForGraph.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/TrafficDecisionType.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/TrafficDecision.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic/DeadlockGuard.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/traffic/DeadlockGuardTest.java` **Interfaces:** - Produces: `wouldCreateCycle(String waiter,Set holders): boolean`. - Produces: `decide(candidate,ledger,topology): TrafficDecision` with `GRANT/HOLD/YIELD` and reason codes. - [ ] **Step 1: Write failing cycle and safe-yield tests** ```java @Test void rejectsGrantThatClosesAWaitCycle() { graph.addWait("V1", "V2"); graph.addWait("V2", "V3"); assertTrue(graph.wouldCreateCycle("V3", Collections.singleton("V1"))); } @Test void doesNotInventYieldPointWhenTopologyHasNone() { TrafficDecision decision = guard.decide(blockedCandidate(), ledger, topologyWithoutSafeWait()); assertEquals(TrafficDecisionType.HOLD, decision.getType()); assertEquals("NO_SAFE_YIELD_POINT", decision.getReasonCode()); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=DeadlockGuardTest test` - [ ] **Step 3: Implement deterministic wait-graph traversal** Use stable vehicle-ID traversal. YIELD may target only topology nodes flagged `safeWait=true`, must not cross occupied resources, and must never alter the business destination. - [ ] **Step 4: Run GREEN** Run: `mvn -pl ruoyi-wcs -am -Dtest=DeadlockGuardTest,ResourceLedgerTest test` - [ ] **Step 5: Commit** ```bash git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/domain/traffic ruoyi-wcs/src/test/java/com/ruoyi/wcs/domain/traffic/DeadlockGuardTest.java git commit -m "feat(wcs): prevent traffic deadlocks" ``` ### Task 7: Orchestrate dispatch decisions and persist traffic state **Files:** - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/dispatch/DispatchCoordinator.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/dispatch/DispatchCycleResult.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/dispatch/DispatchSnapshot.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/dispatch/GrantedDispatch.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/DispatchSnapshotProvider.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/TrafficStateRepository.java` - Create: `ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out/DecisionEventRepository.java` - Create: `sql/wcs/V002__wcs_topology_and_traffic.sql` - Create: `sql/wcs/verify_V002__wcs_topology_and_traffic.sql` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/application/dispatch/DispatchCoordinatorTest.java` - Create: `ruoyi-wcs/src/test/java/com/ruoyi/wcs/infrastructure/persistence/traffic/TrafficConcurrencyIT.java` **Interfaces:** - Produces: `DispatchSnapshotProvider.load(): DispatchSnapshot`; the immutable snapshot contains ready steps, current vehicle snapshots, effective topology, and one optimistic `snapshotVersion`. - Produces: `dispatch(DispatchSnapshot): DispatchCycleResult`. - `DispatchCycleResult.getGranted()` returns `List`; each item contains order ID, step ID, vehicle ID, protected route, reservation IDs, topology version, and fencing token. - Persists: vehicle assignment, route plan, reservations, resource counts, and decision event in one transaction. - Produces: no device command; Plan 3 consumes granted assignments. - [ ] **Step 1: Write a failing two-vehicle arbitration test** ```java @Test void grantsOneVehicleAndHoldsTheOtherForTheSameIntersection() { DispatchCycleResult result = coordinator.dispatch(dispatchSnapshot( Arrays.asList(step("S1", 1), step("S2", 2)), Arrays.asList(vehicle("V1"), vehicle("V2")), topology)); assertEquals(1, result.getGranted().size()); assertEquals(1, result.getHeld().size()); assertEquals(0, result.getInvariantViolations().size()); } ``` - [ ] **Step 2: Run RED** Run: `mvn -pl ruoyi-wcs -am -Dtest=DispatchCoordinatorTest test` - [ ] **Step 3: Implement the coordinator transaction boundary** Load one immutable `DispatchSnapshot`, sort ready steps by priority/age/order ID, assign vehicles, plan routes, call `DeadlockGuard`, then call one repository method: ```java trafficStateRepository.commit(new DispatchCommit( topology.version(), snapshotVersion, assignments, routePlans, reservations, decisionEvents)); ``` If optimistic versions changed, return `RETRY_SNAPSHOT_CHANGED` and persist no partial assignment. - [ ] **Step 4: Add SQL Server tables and locking test** Create `wcs_warehouse`, `wcs_floor`, `wcs_topology_version`, `wcs_topology_node`, `wcs_topology_edge`, `wcs_traffic_zone`, `wcs_resource_definition`, `wcs_device`, `wcs_device_capability`, `wcs_device_port`, `wcs_charger`, `wcs_vehicle_assignment`, `wcs_route_plan`, `wcs_resource_state`, `wcs_resource_reservation`, and `wcs_traffic_decision_event`. The integration test runs two concurrent capacity-1 reservations and asserts exactly one commit succeeds. - [ ] **Step 5: Run full Plan 2 verification** Run: `mvn -pl ruoyi-wcs -am -Dtest='*Topology*,*Capability*,*RoutePlanner*,*VehicleAssignment*,*ResourceLedger*,*DeadlockGuard*,*DispatchCoordinator*' test` Run SQL-tagged tests against the isolated SQL Server: `mvn -pl ruoyi-wcs -Dgroups=sqlserver -Dtest=TrafficConcurrencyIT test`. - [ ] **Step 6: Commit** ```bash git add ruoyi-wcs/src/main/java/com/ruoyi/wcs/application/dispatch ruoyi-wcs/src/main/java/com/ruoyi/wcs/port/out ruoyi-wcs/src/test/java/com/ruoyi/wcs/application/dispatch ruoyi-wcs/src/test/java/com/ruoyi/wcs/infrastructure/persistence/traffic sql/wcs git commit -m "feat(wcs): coordinate and persist traffic decisions" ```