VRD-1 — VRPRunResult has no run_date, so the exit-4 de-risking escalation silently degrades on the option sleeve: every failed CLOSE reads as NEW, every session
status: open · raised 2026-10-21 (implementer, building OSR-1 — adjacent defect found by probing the capability from its actual caller, FILED not fixed) · class: truth divergence between layers · judgement: YES · effort ~0.1 pd · horizon: live now; fires on any VRP session with a failed order
Plain-language summary. Two of the three sleeves return an
ExecutionResult from a run; the option sleeve (VRP) returns its own
VRPRunResult, which its docstring calls "field-compatible with what thales run reads off the daily result." It is not quite: it has no run_date. The
one place thales run reads that field is the FOS-1 / IGD-1 escalation that
decides whether a failed de-risking order is NEW (red the run, exit 4) or
STANDING (the digest's streak line). On VRP that read raises AttributeError
into a try/except whose handler is "unknown = new" — so on the option
sleeve the escalation never consults history at all, and the standing failure line can never print. It is fail-LOUD, so nothing is hidden; but the
NEW-vs-STANDING distinction FOS-1 exists to draw does not exist there, which
is the alarm-fatigue condition its own docstring warns about.
Verified at HEAD fad1d78
src/thales/execution/vrp_daily.py:53-67—@dataclass class VRPRunResultdeclaresequity, skipped, skip_reason, halted, halt_reasons, rejected_orders, orders_submitted, orders_failed, n_target_positions, kill_switch. Norun_date.ExecutionResult(pipeline.py:27-37) declaresrun_date: dateas its FIRST field.src/thales/cli.py(theruncommand) — the onlyresult.run_dateread sits insideif result.orders_failed:→if not dry_run:→try:, and theexceptprints "failure history unreadable … treating every failed de-risking order as NEW". On VRP that branch is reached byAttributeError, not by an unreadable log.- Not yet observed live only because VRP has had no failed order on a session where this path ran; it is a latent, not a theoretical, defect.
Fix shape (propose-only)
Add run_date: date = <today> to VRPRunResult and set it where the wrapper
builds its result, or have cli.run resolve the date the way the OSR-1 sweep
now does (getattr(result, "run_date", None) or market_session_date()). The
first is better: the docstring's claim of field compatibility should be made
true rather than worked around at each caller. A test asserting the two result
types carry the same field names — or at least that every field cli.run
reads exists on both — stops the next divergence.
Test design + negative control
- VRP fixture with one failed CLOSE on two consecutive sessions → the second reads STANDING, not NEW (today: NEW both times). Revert the field → NEW both times returns.
- A structural test over the two dataclasses' field sets, so a third sleeve cannot repeat it. Revert → the test names the missing field.
Kill criterion — pre-registered
If cli.run's only run_date read is removed or the VRP wrapper is retired
before this is built, the row closes as superseded. If a reading of the live
VRP order log shows the sleeve structurally cannot produce a failed order (it
cannot — a refused mleg close is exactly the case), the row closes NO.
Note on provenance
Found while building OSR-1 (legs 1-3, PR for 2026-10-21). The OSR-1 build does NOT fix this — it only avoids the same trap in its own new code, with a comment pointing here. Filed per the implementer's scope rule: adjacent defects get filed, never silently fixed.