PINK DITAv2: kernel-level finiteness guard (no more null-string crash on inf/NaN)

The aborted hard cutover crash-looped with "Rust kernel returned null string" from
process_intent on the first live trading step. Root cause (reproduced): a non-finite
(inf/NaN) numeric field reaching the kernel — Python json.dumps emits the Infinity/NaN
token, serde_json rejects it at parse, and the FFI returned null. Magnitude is fine;
only finiteness was the problem.

Defense in depth, kernel catches it:
- Rust FFI (lib.rs): dita_kernel_process_intent_json / _on_venue_event_json now return
  a clean INVALID_INTENT KernelResult on parse failure (incl. Infinity/NaN tokens) AND
  on serialize failure (a non-finite produced internally) — never a null string.
- Python bridge (rust_backend.py): ExecutionKernel.process_intent validates intent
  finiteness/bounds (target_size, reference_price, limit_price, leverage, exit_leg_ratios;
  size>=0) BEFORE the FFI and rejects INVALID_INTENT, naming the offending field+value.
- contracts.py: add KernelDiagnosticCode.INVALID_INTENT.
- pink_direct.py: on INVALID_INTENT, log full upstream provenance (snapshot.price,
  capital, leverage, sizes) so the numerical SOURCE can be located on the next live run.
- on_venue_event bridge tolerates the fallback's null slot (uses the live slot).

Verified: kernel recompiled; offline 65 + 7 new guard tests green (no regression);
direct-FFI inf payload -> INVALID_INTENT (no null crash). NOTE: this turns the cutover
crash into a clean rejection — the upstream source of the non-finite (the live run's
inf) still needs locating, now aided by the provenance log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-05-31 09:10:13 +02:00
parent 0c15a7698e
commit 9168cf0759
5 changed files with 193 additions and 7 deletions

View File

@@ -427,6 +427,27 @@ class PinkDirectRuntime:
kernel_intent = _decision_to_kernel_intent(decision, intent, slot_id=0)
outcome = self.kernel.process_intent(kernel_intent)
# Locate the source of any non-finite intent the kernel rejected:
# log the full upstream provenance (snapshot price, account capital,
# leverage, sizing) so a numerical error can be traced to its origin
# rather than silently rejected.
if outcome.diagnostic_code == KernelDiagnosticCode.INVALID_INTENT:
self.logger.error(
"INVALID_INTENT rejected by kernel: %s | provenance: "
"snapshot.price=%r capital=%r open_positions=%r leverage=%r "
"target_size=%r reference_price=%r limit_price=%r action=%s asset=%s",
dict(outcome.details or {}),
getattr(snapshot, "price", None),
context.capital,
context.open_positions,
getattr(kernel_intent, "leverage", None),
getattr(kernel_intent, "target_size", None),
getattr(kernel_intent, "reference_price", None),
getattr(kernel_intent, "limit_price", None),
decision.action.value,
intent.asset,
)
# Read authoritative final state from kernel.
final_slot = self.kernel.slot(0)
slot_dict = final_slot.to_dict()