diff --git a/scripts/read_binance_account_facts.py b/scripts/read_binance_account_facts.py index a4b9da03..96f265ea 100644 --- a/scripts/read_binance_account_facts.py +++ b/scripts/read_binance_account_facts.py @@ -614,8 +614,7 @@ def validate_strategy_report(report: Mapping[str, Any], runtime_target: Mapping[ or not isinstance(report.get("run_id"), str) or not report["run_id"].strip() or report.get("execution_blocked_reason") - or not isinstance(errors, Mapping) - or errors.get("errors") != [] + or errors not in ({}, {"errors": []}) or not isinstance(side_effects, Mapping) or type(side_effects.get("executed_call_count")) is not int or side_effects.get("executed_call_count") < 0 @@ -700,7 +699,6 @@ def _target_identity(raw: str) -> dict[str, Any]: raise _stop("runtime_target_invalid") try: from quant_platform_kit.common.runtime_target import resolve_runtime_target_from_env - from quant_platform_kit.common.live_continuity import runtime_target_permits_standard_execution target = resolve_runtime_target_from_env( env={"RUNTIME_TARGET_JSON": raw}, expected_platform_id="binance" @@ -710,7 +708,6 @@ def _target_identity(raw: str) -> dict[str, Any]: value = target.to_dict() if ( target.dry_run_only - or not runtime_target_permits_standard_execution(target) or not isinstance(value.get("account_scope"), str) or not value["account_scope"] or value["account_scope"] == "default" diff --git a/tests/test_binance_account_facts.py b/tests/test_binance_account_facts.py index 09830852..1db1f097 100644 --- a/tests/test_binance_account_facts.py +++ b/tests/test_binance_account_facts.py @@ -662,6 +662,35 @@ def test_runtime_target_and_protected_binding_match_canonical_selector_shape(): assert reader._binding(json.dumps(binding), target, UID_DIGEST, READER_SHA) == binding +def test_runtime_target_accepts_reconcile_only_with_valid_continuity_fingerprint(): + from quant_platform_kit.common.live_continuity import runtime_target_fingerprint + from scripts import read_binance_account_facts as reader + + target = { + "platform_id": "binance", + "strategy_profile": "synthetic-profile", + "dry_run_only": False, + "deployment_selector": "synthetic-deployment", + "account_selector": ["synthetic-account"], + "account_scope": "synthetic-scope", + "service_name": "synthetic-service", + } + continuity = { + "state": "RECONCILE_ONLY", + "baseline_kind": "legacy_authorized", + "baseline_id": "synthetic-baseline", + "baseline_target_sha256": runtime_target_fingerprint(target), + "captured_at": "2026-10-02", + } + target["live_continuity"] = continuity + resolved = reader._target_identity(json.dumps(target)) + assert resolved["live_continuity"]["state"] == "RECONCILE_ONLY" + + target["live_continuity"] = {**continuity, "baseline_target_sha256": "0" * 64} + with pytest.raises(reader.ReaderError, match="runtime_target_invalid"): + reader._target_identity(json.dumps(target)) + + def _eligible_strategy_report(*, receipt_outcome="no_action", confirmation=None, observation=None): from quant_platform_kit.common.execution_receipts import build_execution_receipt @@ -717,6 +746,40 @@ def test_strategy_report_accepts_closed_execution_receipt(): reader.validate_strategy_report(report, target) +@pytest.mark.parametrize("error_summary", [{}, {"errors": []}]) +def test_strategy_report_accepts_only_exact_empty_error_summary_shapes(error_summary): + from scripts import read_binance_account_facts as reader + + report, target = _eligible_strategy_report() + report["error_summary"] = error_summary + reader.validate_strategy_report(report, target) + + +@pytest.mark.parametrize("error_summary", [ + None, + [], + {"errors": None}, + {"errors": ["synthetic-error"]}, + {"errors": [], "extra": "synthetic"}, +]) +def test_strategy_report_rejects_invalid_or_open_error_summary_shapes(error_summary): + from scripts import read_binance_account_facts as reader + + report, target = _eligible_strategy_report() + report["error_summary"] = error_summary + with pytest.raises(reader.ReaderError, match="strategy_report_not_eligible"): + reader.validate_strategy_report(report, target) + + +def test_strategy_report_rejects_missing_error_summary(): + from scripts import read_binance_account_facts as reader + + report, target = _eligible_strategy_report() + del report["error_summary"] + with pytest.raises(reader.ReaderError, match="strategy_report_not_eligible"): + reader.validate_strategy_report(report, target) + + def test_strategy_report_accepts_missing_receipt_only_for_explicit_zero_counters(): from scripts import read_binance_account_facts as reader