From 460337cdac48d6f55ac39d1b93fbc2d48e945c31 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Mon, 31 Aug 2026 00:36:56 +0800 Subject: [PATCH] fix: preserve validated IBKR reconciliation baseline Co-Authored-By: Codex --- application/broker_reconciliation.py | 16 +++++----- tests/test_broker_reconciliation.py | 44 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/application/broker_reconciliation.py b/application/broker_reconciliation.py index 74b0bd0..229f0f6 100644 --- a/application/broker_reconciliation.py +++ b/application/broker_reconciliation.py @@ -24,7 +24,6 @@ evaluate_broker_reconciliation_recovery, ) from quant_platform_kit.common.execution_state import build_execution_marker_store_from_env -from quant_platform_kit.common.live_continuity import runtime_target_fingerprint class IBKRReconciliationReadError(RuntimeError): @@ -383,13 +382,14 @@ def _continuity_fields(runtime_target: Any) -> tuple[str, str, str]: raise IBKRReconciliationReadError( "IBKR reconciliation live-continuity baseline is incomplete." ) - try: - runtime_target_sha256 = runtime_target_fingerprint(runtime_target.to_dict()) - except Exception as exc: - raise IBKRReconciliationReadError( - "IBKR reconciliation could not fingerprint the current runtime target." - ) from exc - return baseline_id, baseline_target_sha256, runtime_target_sha256 + # ``resolve_runtime_target_from_env`` verifies this digest against the + # original RUNTIME_TARGET_JSON before it returns RuntimeTarget. Do not + # re-fingerprint ``RuntimeTarget.to_dict()`` here: that representation + # adds derived execution fields, so a legacy JSON baseline could be + # falsely reported as changed even though the deployed target is valid. + # The broker evidence remains strictly bound to the startup-validated + # baseline; this only removes a representation-level false mismatch. + return baseline_id, baseline_target_sha256, baseline_target_sha256 def build_reconciliation_candidate( diff --git a/tests/test_broker_reconciliation.py b/tests/test_broker_reconciliation.py index d23f828..e595d33 100644 --- a/tests/test_broker_reconciliation.py +++ b/tests/test_broker_reconciliation.py @@ -173,6 +173,31 @@ def _frozen_runtime_target(): ) +def _frozen_runtime_target_from_minimal_json(): + """Mirror a valid legacy target JSON that omits derived execution fields.""" + + payload = { + "platform_id": "ibkr", + "strategy_profile": "soxl_soxx_trend_income", + "dry_run_only": False, + "deployment_selector": "live", + "account_selector": ["group"], + "account_scope": "live", + "service_name": "ibkr-live", + } + return build_runtime_target( + **payload, + live_continuity={ + "state": "RECONCILE_ONLY", + "baseline_kind": "legacy_authorized", + "baseline_id": "ibkr-soxl-lkg-20260830", + "baseline_target_sha256": runtime_target_fingerprint(payload), + "captured_at": "2026-08-30", + }, + continuity_fingerprint_payload=payload, + ) + + def _observations() -> IBKRReconciliationObservations: return IBKRReconciliationObservations( account_scope={"account_ids": ["U123"]}, @@ -210,6 +235,25 @@ def test_candidate_stays_frozen_without_private_expected_digests(tmp_path) -> No assert candidate.to_safe_dict()["evidence"]["positions_sha256"] +def test_candidate_accepts_startup_validated_legacy_json_baseline(tmp_path) -> None: + candidate = build_reconciliation_candidate( + observations=_observations(), + runtime_target=_frozen_runtime_target_from_minimal_json(), + platform_id="ibkr", + strategy_profile="soxl_soxx_trend_income", + account_group="LIVE", + project_id=None, + env_reader=lambda name, default=None: ( + str(tmp_path) if name == "IBKR_EXECUTION_STATE_DIR" else default + ), + ) + + assert "broker_reconciliation_baseline_target_mismatch" not in { + finding.value for finding in candidate.recovery_blockers + } + assert candidate.permits_active_lkg is False + + def test_candidate_can_only_pass_with_all_matching_private_digests(tmp_path) -> None: target = _frozen_runtime_target()