94 lines
5.7 KiB
Markdown
94 lines
5.7 KiB
Markdown
# FT546 WMS Shadow-Only Design
|
|
|
|
## Goal
|
|
|
|
Create a clean field-test branch directly from `origin/main` that runs the Java storage algorithm only as an observer. The existing Legacy WMS stored procedures remain the sole owners of inbound and outbound decisions and business writes.
|
|
|
|
## Safety invariants
|
|
|
|
1. The branch is based on `origin/main` and contains no license, UI, TLS, tidy, replay, PDA, WCS callback, or ACTIVE workflow integration changes.
|
|
2. Legacy stored procedures are called exactly once with the same inputs and their return values remain authoritative.
|
|
3. Shadow planning runs before the Legacy allocation so it observes the pre-decision state, but it runs in a separate `REQUIRES_NEW`, `READ_UNCOMMITTED` transaction with a configurable timeout.
|
|
4. A shadow exception, timeout, missing optional input, missing algorithm object, or comparison logging failure is caught and logged; it cannot skip, replace, roll back, or rewrite the Legacy result.
|
|
5. No ACTIVE entry point is exposed by `ruoyi-admin`. Startup rejects any algorithm mode other than `SHADOW` when shadow observation is enabled.
|
|
6. Shadow mode writes only `alg.*` read-model and diagnostic objects. It never calls reservation, tidy, deep-workflow, replay, move-completion, or other procedures that write WMS business tables.
|
|
7. Shadow observation is disabled by default and must be enabled explicitly in the IDEA Run Configuration.
|
|
8. At most one shadow planning operation runs per JVM. Concurrent observations are skipped immediately without queueing or delaying Legacy allocation.
|
|
|
|
## Architecture
|
|
|
|
The existing `fortucky-algorithm` planner and JDBC read model are reused as an internal dependency. A new `ruoyi-admin` shadow adapter owns the integration boundary:
|
|
|
|
- `ShadowObservationProperties` controls enablement, warehouse fallback, and timeout.
|
|
- `ShadowSafetyGuard` validates the effective mode at startup.
|
|
- `ShadowPlanningObserver` performs fail-open inbound/outbound planning in an independent transaction, admits at most one plan per JVM, and returns an immutable observation token.
|
|
- Legacy services invoke the observer immediately before their existing stored procedure, execute the original Legacy code unchanged, then submit the Legacy result for comparison logging.
|
|
|
|
There are no algorithm controllers, ACTIVE schedulers, workflow callbacks, reservation calls, or changes to the frontend.
|
|
|
|
## Data flow
|
|
|
|
### Inbound
|
|
|
|
1. Load the same inbound job that `origin/main` already loads.
|
|
2. Ask the shadow observer to compute a proposed location. Failures become a `shadow planning failed` log entry.
|
|
3. Invoke the existing `allotInJobSpace` stored procedure exactly once.
|
|
4. Reload and return the Legacy job exactly as `origin/main` does.
|
|
5. Log the proposed shadow location beside the Legacy location.
|
|
|
|
### Outbound
|
|
|
|
1. Before each existing automatic or manual Legacy allocation call, ask the observer for a plan.
|
|
2. Missing project/stock data is recorded as a shadow rejection; it never blocks the Legacy procedure.
|
|
3. Invoke the original `autoAllot` or `manualAllot` procedure exactly once.
|
|
4. Preserve the original success/warning response construction.
|
|
5. Log the shadow status/code beside the Legacy procedure code.
|
|
|
|
## Transaction and failure isolation
|
|
|
|
Shadow planning uses `TransactionTemplate` with `PROPAGATION_REQUIRES_NEW`, `ISOLATION_READ_UNCOMMITTED`, read-model writes enabled, and a short timeout. The transaction completes or rolls back before Legacy allocation starts, so algorithm locks and rollback state cannot leak into the Legacy transaction. A JVM-local nonblocking gate permits one planning call at a time; a concurrent call returns an empty observation immediately, while successful and failed calls both release the gate in `finally`. Comparison logging is not gated. The adapter catches runtime failures at its public boundary and returns an empty observation.
|
|
|
|
The integration never queries `alg.tidy_mode` and never uses an update lock for admission control.
|
|
|
|
## Shadow database package
|
|
|
|
`install-shadow.sql` creates only:
|
|
|
|
- policy/read-model/diagnostic tables required by inbound and outbound planning;
|
|
- read-only views over WMS business data;
|
|
- topology, snapshot, lane-state, full-refresh, and decision-log support;
|
|
- no triggers on any Legacy WMS table and no dirty-lane queue;
|
|
- initial read-model synchronization.
|
|
|
|
It excludes tidy, replay, reservation mutation procedures, deep workflow, outbound workflow, WCS completion, PDA callback, and any stored procedure that inserts, updates, or deletes WMS business rows.
|
|
|
|
`uninstall-shadow.sql` removes only shadow-owned `alg` objects and the `alg` schema. It never modifies WMS business rows. Operators must export `alg.decision_log` before uninstalling.
|
|
|
|
## Configuration
|
|
|
|
The IDEA field-test configuration sets:
|
|
|
|
```text
|
|
FORTUCKY_SHADOW_ENABLED=true
|
|
FORTUCKY_ALGORITHM_MODE=SHADOW
|
|
FORTUCKY_ALGORITHM_DATABASE_WRITES_ENABLED=true
|
|
FORTUCKY_ALGORITHM_WAREHOUSE_ID=<field warehouse>
|
|
FORTUCKY_SHADOW_TIMEOUT_SECONDS=3
|
|
```
|
|
|
|
Replay variables remain unset. The stable `origin/main` Run Configuration is kept in a separate source directory for immediate rollback.
|
|
|
|
## Verification
|
|
|
|
Automated tests prove:
|
|
|
|
- observer failure still invokes Legacy exactly once;
|
|
- missing project/stock input still invokes Legacy exactly once;
|
|
- Legacy result and response structure remain authoritative;
|
|
- SHADOW configuration rejects ACTIVE mode;
|
|
- shadow SQL contains only allow-listed `alg` objects, no triggers, and no WMS business-table DML;
|
|
- install/uninstall object lists are symmetrical;
|
|
- existing project tests and packaging still succeed.
|
|
|
|
Field verification compares inbound/outbound shadow logs with Legacy results, monitors SQL latency and locks, and stops immediately if normal WMS behavior differs from `origin/main`.
|