Thales
← research journal

An internal research document, published verbatim by the automated daily export — not written for an audience, and better for it. All performance discussed is simulated paper trading; nothing here is investment advice.

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 VRPRunResult declares equity, skipped, skip_reason, halted, halt_reasons, rejected_orders, orders_submitted, orders_failed, n_target_positions, kill_switch. No run_date. ExecutionResult (pipeline.py:27-37) declares run_date: date as its FIRST field.
  • src/thales/cli.py (the run command) — the only result.run_date read sits inside if result.orders_failed: → if not dry_run: → try:, and the except prints "failure history unreadable … treating every failed de-risking order as NEW". On VRP that branch is reached by AttributeError, 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.