These files bypassed the i18n table entirely, so a user running LANG=en still
got Chinese order logs. Migrating them surfaced structural duplication too: the
five order paths each spelled out their own 'quantity invalid' string, now one
key parameterised by an order-kind label.
Also fixes a type that carried display text as its domain: TrendLabel was
'做多' | '做空' | '无信号', so the engine's snapshot value *was* the Chinese
string and English rendering depended on matching it. Now 'long' | 'short' |
'none', translated at the edge.
Order-coordinator tests asserted the Chinese literals, which is exactly what
made the gap invisible; they now assert the resolved key so they hold in either
language.
39 new translation keys. 271 pass; tsc and oxlint clean.
Two more clusters lifted out of the engine, both defined by latches whose only
correctness property is that they move together:
- TokenExpiryGuard owns the five flags (state, logged, notified, cancelDone,
closeOnly) that make each consequence of an expired StandX token happen once
per episode and re-arm when a fresh token arrives. evaluate() returns a
decision instead of a bare boolean, so the tick reads what it means.
- IsolatedMarginGuard owns the single in-flight switch promise that stops
concurrent ticks from stacking margin-mode change requests, plus the
poll-until-confirmed loop.
Both were previously reachable only through a live adapter; they now have 22
unit tests between them, covering the latch reset across a token renewal, the
cancel retry after a failure, unknown-order treated as success, and the
concurrent-tick sharing of one margin switch.
Confirm cadence kept at 500ms x 10 to match the engine's original constants.
271 pass; tsc and oxlint clean. Engine 1939 -> 1826 lines.