From 94f5566c081d11671390e964a72c9a7548649144 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Mon, 31 Aug 2026 03:24:39 +0800 Subject: [PATCH 1/2] feat: add verify-only reconciliation recovery controller Co-Authored-By: Codex --- ...econciliation_baseline_enrollment.zh-CN.md | 14 + scripts/verify_reconciliation_recovery.py | 252 ++++++++++++++++++ ...est_reconciliation_recovery_verify_only.py | 214 +++++++++++++++ 3 files changed, 480 insertions(+) create mode 100644 scripts/verify_reconciliation_recovery.py create mode 100644 tests/test_reconciliation_recovery_verify_only.py diff --git a/docs/ibkr_reconciliation_baseline_enrollment.zh-CN.md b/docs/ibkr_reconciliation_baseline_enrollment.zh-CN.md index 0cbeddc..54d119d 100644 --- a/docs/ibkr_reconciliation_baseline_enrollment.zh-CN.md +++ b/docs/ibkr_reconciliation_baseline_enrollment.zh-CN.md @@ -31,3 +31,17 @@ AIAuditBridge 的完整 `reconciliation_baseline` 输出。它会同时核验: 该写入仅供人工确认队列使用,不能恢复 `ACTIVE_LKG`。后续私有控制器仍须重新读取 券商、复核双审绑定,并以原子比较并设置方式转换状态;任一失败都保持 `RECONCILE_ONLY`。 + +## 私有验证器(暂不写状态) + +`scripts/verify_reconciliation_recovery.py` 是恢复链路的第二层。它使用一枚不同于 +来源发布令牌的 `RECONCILIATION_RECOVERY_CONTROLLER_TOKEN`,从 QRS 只读取得已确认 +条目;然后重新解析本地私有候选与完整双审回执,读取一份**确认之后**新生成的 +`/reconcile` 回执,并核对已部署 `RUNTIME_TARGET_JSON` 仍为同一 +`RECONCILE_ONLY` 基线。 + +该验证器只输出 `ibkr_reconciliation_recovery_verification.v1` 和可能的 QPK 原子切换 +计划。即使验证通过,输出也固定 `controller_mode=verify_only`、`no_order=true`、 +`execution_authority_granted=false`、`state_write_attempted=false`;它没有 Cloud Run、 +券商、执行标记或订单写入代码。下一层单独的最小权限控制器才可消费该计划,并且仍要 +在同一目标上比较五项摘要后执行一次精确 CAS。 diff --git a/scripts/verify_reconciliation_recovery.py b/scripts/verify_reconciliation_recovery.py new file mode 100644 index 0000000..7683ceb --- /dev/null +++ b/scripts/verify_reconciliation_recovery.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +"""Verify one confirmed IBKR legacy-recovery request without applying it. + +The verifier is a private-control-plane building block. It reads the bounded +QRS confirmation endpoint, a full AIAuditBridge mandatory review receipt, a +private QPK baseline candidate, the currently deployed runtime target, and a +new read-only ``/reconcile`` receipt. It only returns a QPK transition *plan* +when every value agrees; it never updates Cloud Run, a runtime target, a +broker setting, an execution marker, or an order. +""" + +from __future__ import annotations + +import argparse +import json +import os +from collections.abc import Callable, Mapping +from datetime import datetime, timezone +from pathlib import Path +from typing import Any +from urllib.error import HTTPError, URLError +from urllib.parse import urlencode +from urllib.request import HTTPRedirectHandler, Request, build_opener + +from quant_platform_kit.common.reconciliation_recovery import ( + ReconciliationRecoveryConfirmation, + evaluate_reconciliation_recovery_activation, +) + +from scripts.build_reconciliation_baseline_candidate import extract_reconciliation_evidence +from scripts.publish_reconciliation_recovery_source import ( + _load_json, + _parse_time, + extract_baseline_candidate, + extract_bound_dual_review, +) + + +RECONCILIATION_RECOVERY_CONTROLLER_TOKEN_ENV = "RECONCILIATION_RECOVERY_CONTROLLER_TOKEN" +CONTROLLER_READ_SCHEMA_VERSION = "qsl_reconciliation_recovery_controller_read.v1" +VERIFY_ONLY_SCHEMA_VERSION = "ibkr_reconciliation_recovery_verification.v1" + + +class _NoRedirect(HTTPRedirectHandler): + """Do not forward a controller token to an HTTP redirect destination.""" + + def redirect_request(self, request: Request, *_args: object, **_kwargs: object) -> None: + return None + + +def _require_controller_read_url(value: str) -> str: + normalized = str(value or "").strip().rstrip("/") + expected_suffix = "/api/internal/reconciliation-recovery-confirmation" + if not normalized.startswith("https://"): + raise ValueError("confirmation_url must use https") + if not normalized.endswith(expected_suffix): + raise ValueError("confirmation_url must target the reconciliation recovery controller read endpoint") + return normalized + + +def read_console_confirmation( + *, + confirmation_url: str, + recovery_id: str, + token: str, + opener: Callable[..., Any] | None = None, +) -> dict[str, object]: + """Read one current confirmation from QRS using its dedicated token.""" + + normalized_url = _require_controller_read_url(confirmation_url) + normalized_token = str(token or "").strip() + if not normalized_token: + raise ValueError(f"{RECONCILIATION_RECOVERY_CONTROLLER_TOKEN_ENV} is required") + request = Request( + f"{normalized_url}?{urlencode({'recovery_id': recovery_id})}", + headers={"Authorization": f"Bearer {normalized_token}"}, + method="GET", + ) + request_opener = opener or build_opener(_NoRedirect).open + try: + with request_opener(request, timeout=15) as response: + status_code = int(response.status) + raw_body = response.read().decode("utf-8") + except HTTPError as exc: + raise RuntimeError(f"reconciliation recovery confirmation read returned HTTP {exc.code}") from exc + except URLError as exc: + raise RuntimeError("reconciliation recovery confirmation read failed") from exc + if status_code < 200 or status_code >= 300: + raise RuntimeError(f"reconciliation recovery confirmation read returned HTTP {status_code}") + try: + payload = json.loads(raw_body) + except json.JSONDecodeError as exc: + raise RuntimeError("reconciliation recovery confirmation read returned invalid JSON") from exc + if not isinstance(payload, dict) or payload.get("ok") is not True: + raise RuntimeError("reconciliation recovery confirmation was not acknowledged") + return payload + + +def _runtime_target_state( + payload: Mapping[str, Any], + *, + candidate: Any, +) -> tuple[str, tuple[str, ...]]: + """Check only the non-sensitive deployed runtime identity and continuity state.""" + + findings: list[str] = [] + if str(payload.get("platform_id") or "").strip() != candidate.platform_id: + findings.append("ibkr_reconciliation_runtime_target_platform_mismatch") + if str(payload.get("strategy_profile") or "").strip() != candidate.strategy_profile: + findings.append("ibkr_reconciliation_runtime_target_strategy_mismatch") + continuity = payload.get("live_continuity") + if not isinstance(continuity, Mapping): + return "", tuple(findings + ["ibkr_reconciliation_runtime_target_continuity_missing"]) + state = str(continuity.get("state") or "").strip().upper() + if str(continuity.get("baseline_id") or "").strip() != candidate.baseline_id: + findings.append("ibkr_reconciliation_runtime_target_baseline_mismatch") + if str(continuity.get("baseline_target_sha256") or "").strip().lower() != candidate.baseline_target_sha256: + findings.append("ibkr_reconciliation_runtime_target_baseline_digest_mismatch") + return state, tuple(dict.fromkeys(findings)) + + +def _verify_console_response( + payload: Mapping[str, Any], + *, + recovery_id: str, + candidate: Any, +) -> ReconciliationRecoveryConfirmation: + if payload.get("schema_version") != CONTROLLER_READ_SCHEMA_VERSION: + raise ValueError("controller confirmation response has an unsupported schema_version") + policy = payload.get("policy") + if not isinstance(policy, Mapping) or policy != { + "no_order": True, + "execution_authority_granted": False, + "controller_must_reverify": True, + }: + raise ValueError("controller confirmation response has an invalid non-execution policy") + recovery = payload.get("recovery") + if not isinstance(recovery, Mapping): + raise ValueError("controller confirmation response is missing recovery metadata") + if str(recovery.get("recovery_id") or "").strip() != recovery_id: + raise ValueError("controller confirmation recovery_id mismatch") + if str(recovery.get("platform") or "").strip() != "ibkr": + raise ValueError("controller confirmation platform mismatch") + if str(recovery.get("strategy_profile") or "").strip() != candidate.strategy_profile: + raise ValueError("controller confirmation strategy_profile mismatch") + if str(recovery.get("environment") or "").strip().lower() != "live": + raise ValueError("controller confirmation environment mismatch") + if str(recovery.get("reconciliation_state") or "").strip().upper() != "RECONCILE_ONLY": + raise ValueError("controller confirmation is not reconcile-only") + if str(recovery.get("candidate_sha256") or "").strip().lower() != candidate.candidate_sha256: + raise ValueError("controller confirmation candidate binding mismatch") + if str(recovery.get("dual_review_binding_sha256") or "").strip().lower() != candidate.candidate_sha256: + raise ValueError("controller confirmation dual review binding mismatch") + if int(recovery.get("evidence_sample_count") or 0) < 2: + raise ValueError("controller confirmation evidence sample count is insufficient") + confirmation = payload.get("confirmation") + if not isinstance(confirmation, Mapping): + raise ValueError("controller confirmation response is missing confirmation") + return ReconciliationRecoveryConfirmation.from_dict(confirmation) + + +def verify_reconciliation_recovery( + *, + candidate_payload: Mapping[str, Any], + dual_review_payload: Mapping[str, Any], + confirmation_payload: Mapping[str, Any], + current_receipt_payload: Mapping[str, Any], + runtime_target_payload: Mapping[str, Any], + recovery_id: str, + now: datetime | None = None, +) -> dict[str, object]: + """Return a non-executable recovery verification result. + + The caller owns how the fresh broker receipt and deployed target are read; + this function intentionally has no broker, Cloud Run, or state-write port. + """ + + candidate = extract_baseline_candidate(candidate_payload) + # Re-parse and validate the full audit receipt rather than trusting the + # redacted QRS source. The return value is intentionally discarded: its + # successful construction is the independent binding proof for this step. + extract_bound_dual_review(dual_review_payload, candidate=candidate) + confirmation = _verify_console_response( + confirmation_payload, + recovery_id=recovery_id, + candidate=candidate, + ) + current_evidence = extract_reconciliation_evidence(current_receipt_payload) + continuity_state, target_findings = _runtime_target_state(runtime_target_payload, candidate=candidate) + evaluation = evaluate_reconciliation_recovery_activation( + recovery_id=recovery_id, + candidate=candidate, + confirmation=confirmation, + current_evidence=current_evidence, + current_live_continuity_state=continuity_state, + dual_review_binding_reverified=True, + now=now or datetime.now(timezone.utc), + ) + findings = tuple(dict.fromkeys((*target_findings, *evaluation.findings))) + plan = evaluation.transition_plan if not findings else None + return { + "schema_version": VERIFY_ONLY_SCHEMA_VERSION, + "recovery_id": recovery_id, + "candidate_sha256": candidate.candidate_sha256, + "confirmation_sha256": confirmation.confirmation_sha256, + "ready_for_atomic_state_transition": not findings and plan is not None, + "findings": list(findings), + "transition_plan": plan.to_dict() if plan is not None else None, + "policy": { + "controller_mode": "verify_only", + "no_order": True, + "execution_authority_granted": False, + "state_write_attempted": False, + }, + } + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="Verify a confirmed IBKR legacy recovery without changing broker or runtime state." + ) + parser.add_argument("--candidate", type=Path, required=True, help="Private QPK baseline-candidate output") + parser.add_argument("--dual-review", type=Path, required=True, help="Private full AIAuditBridge dual-review output") + parser.add_argument("--current-receipt", type=Path, required=True, help="Fresh post-confirmation private /reconcile receipt") + parser.add_argument("--runtime-target", type=Path, required=True, help="Read-only deployed RUNTIME_TARGET_JSON snapshot") + parser.add_argument("--confirmation-url", required=True, help="QRS private controller-read HTTPS endpoint") + parser.add_argument("--recovery-id", required=True) + parser.add_argument("--now", help="Optional ISO-8601 time used for deterministic validation") + args = parser.parse_args(argv) + try: + confirmation = read_console_confirmation( + confirmation_url=args.confirmation_url, + recovery_id=args.recovery_id, + token=os.environ.get(RECONCILIATION_RECOVERY_CONTROLLER_TOKEN_ENV, ""), + ) + result = verify_reconciliation_recovery( + candidate_payload=_load_json(args.candidate, label="baseline candidate"), + dual_review_payload=_load_json(args.dual_review, label="dual review"), + confirmation_payload=confirmation, + current_receipt_payload=_load_json(args.current_receipt, label="current reconciliation receipt"), + runtime_target_payload=_load_json(args.runtime_target, label="runtime target"), + recovery_id=args.recovery_id, + now=_parse_time(args.now), + ) + except (ValueError, RuntimeError) as exc: + parser.error(str(exc)) + print(json.dumps(result, ensure_ascii=False, sort_keys=True)) + return 0 if result["ready_for_atomic_state_transition"] else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_reconciliation_recovery_verify_only.py b/tests/test_reconciliation_recovery_verify_only.py new file mode 100644 index 0000000..6d2c153 --- /dev/null +++ b/tests/test_reconciliation_recovery_verify_only.py @@ -0,0 +1,214 @@ +from __future__ import annotations + +import json +from datetime import datetime, timedelta, timezone + +from quant_platform_kit.common.broker_reconciliation import build_broker_reconciliation_evidence +from quant_platform_kit.common.broker_reconciliation_enrollment import ( + evaluate_broker_reconciliation_baseline_enrollment, +) +from quant_platform_kit.common.reconciliation_recovery import ( + calculate_reconciliation_recovery_confirmation_sha256, +) + +from scripts.verify_reconciliation_recovery import ( + read_console_confirmation, + verify_reconciliation_recovery, +) + + +def _digest(character: str) -> str: + return character * 64 + + +def _evidence(*, observed_at: datetime) -> object: + return build_broker_reconciliation_evidence( + platform_id="ibkr", + strategy_profile="soxl_soxx_trend_income", + account_scope_sha256=_digest("a"), + baseline_id="soxl-ibkr-lkg-20260830", + baseline_target_sha256=_digest("b"), + runtime_target_sha256=_digest("b"), + observed_at=observed_at, + broker_connected=True, + account_identity_match=True, + positions_match=True, + cash_match=True, + open_orders_match=True, + recent_executions_match=True, + local_execution_ledger_match=True, + positions_sha256=_digest("c"), + cash_sha256=_digest("d"), + open_orders_sha256=_digest("e"), + recent_executions_sha256=_digest("f"), + local_execution_ledger_sha256=_digest("0"), + ) + + +def _candidate_payload(start: datetime) -> dict[str, object]: + enrollment = evaluate_broker_reconciliation_baseline_enrollment( + [_evidence(observed_at=start), _evidence(observed_at=start + timedelta(minutes=2))], + now=start + timedelta(minutes=3), + ) + assert enrollment.candidate is not None + return { + "schema_version": "ibkr_reconciliation_baseline_enrollment.v1", + "ready_for_independent_review": True, + "findings": [], + "candidate": enrollment.candidate.to_dict(), + } + + +def _dual_review(candidate_sha256: str) -> dict[str, object]: + return { + "trigger": "reconciliation_baseline", + "strategy_profile": "soxl_soxx_trend_income", + "primary_review": {"verdict": "approve", "confidence": 0.99}, + "secondary_review": { + "mode": "dual_api", + "gpt": {"verdict": "approve", "confidence": 0.98}, + "claude": {"verdict": "approve", "confidence": 0.98}, + }, + "escalated": True, + "outcome": "pass", + "evidence_binding_sha256": candidate_sha256, + "requires_human_recovery_approval": True, + "recovery_authority": {"human_review_required": True, "final_action": "escalate"}, + } + + +def _confirmation(candidate_sha256: str, *, confirmed_at: datetime) -> dict[str, object]: + value: dict[str, object] = { + "schema_version": "qsl_reconciliation_recovery_confirmation.v1", + "recovery_id": "ibkr-soxl-live-recovery", + "candidate_sha256": candidate_sha256, + "dual_review_binding_sha256": candidate_sha256, + "confirmed_at": confirmed_at.isoformat().replace("+00:00", "Z"), + "confirmed_by": "recovery-admin", + "no_order": True, + "execution_authority_granted": False, + "confirmation_sha256": "0" * 64, + } + value["confirmation_sha256"] = calculate_reconciliation_recovery_confirmation_sha256(value) + return value + + +def _console_response(candidate_sha256: str, *, confirmed_at: datetime) -> dict[str, object]: + return { + "ok": True, + "schema_version": "qsl_reconciliation_recovery_controller_read.v1", + "recovery": { + "recovery_id": "ibkr-soxl-live-recovery", + "platform": "ibkr", + "strategy_profile": "soxl_soxx_trend_income", + "environment": "live", + "reconciliation_state": "RECONCILE_ONLY", + "candidate_sha256": candidate_sha256, + "dual_review_binding_sha256": candidate_sha256, + "evidence_sample_count": 2, + "first_observed_at": "2026-08-31T01:00:00Z", + "last_observed_at": "2026-08-31T01:02:00Z", + }, + "confirmation": _confirmation(candidate_sha256, confirmed_at=confirmed_at), + "policy": { + "no_order": True, + "execution_authority_granted": False, + "controller_must_reverify": True, + }, + } + + +def _runtime_target(*, state: str = "RECONCILE_ONLY") -> dict[str, object]: + return { + "platform_id": "ibkr", + "strategy_profile": "soxl_soxx_trend_income", + "live_continuity": { + "state": state, + "baseline_id": "soxl-ibkr-lkg-20260830", + "baseline_target_sha256": _digest("b"), + }, + } + + +def test_verify_only_returns_a_plan_without_attempting_state_write() -> None: + start = datetime(2026, 8, 31, 1, 0, tzinfo=timezone.utc) + candidate = _candidate_payload(start) + candidate_value = candidate["candidate"] + assert isinstance(candidate_value, dict) + candidate_sha256 = str(candidate_value["candidate_sha256"]) + + result = verify_reconciliation_recovery( + candidate_payload=candidate, + dual_review_payload=_dual_review(candidate_sha256), + confirmation_payload=_console_response(candidate_sha256, confirmed_at=start + timedelta(minutes=3)), + current_receipt_payload={"evidence": _evidence(observed_at=start + timedelta(minutes=4)).to_dict()}, + runtime_target_payload=_runtime_target(), + recovery_id="ibkr-soxl-live-recovery", + now=start + timedelta(minutes=5), + ) + + assert result["ready_for_atomic_state_transition"] is True + assert result["findings"] == [] + assert result["transition_plan"]["next_live_continuity_state"] == "ACTIVE_LKG" # type: ignore[index] + assert result["policy"] == { + "controller_mode": "verify_only", + "no_order": True, + "execution_authority_granted": False, + "state_write_attempted": False, + } + + +def test_verify_only_keeps_plan_closed_when_deployed_target_is_not_reconcile_only() -> None: + start = datetime(2026, 8, 31, 1, 0, tzinfo=timezone.utc) + candidate = _candidate_payload(start) + candidate_value = candidate["candidate"] + assert isinstance(candidate_value, dict) + candidate_sha256 = str(candidate_value["candidate_sha256"]) + + result = verify_reconciliation_recovery( + candidate_payload=candidate, + dual_review_payload=_dual_review(candidate_sha256), + confirmation_payload=_console_response(candidate_sha256, confirmed_at=start + timedelta(minutes=3)), + current_receipt_payload={"evidence": _evidence(observed_at=start + timedelta(minutes=4)).to_dict()}, + runtime_target_payload=_runtime_target(state="PAUSED"), + recovery_id="ibkr-soxl-live-recovery", + now=start + timedelta(minutes=5), + ) + + assert result["ready_for_atomic_state_transition"] is False + assert result["transition_plan"] is None + assert "reconciliation_recovery_current_state_not_reconcile_only" in result["findings"] + + +def test_read_console_confirmation_uses_dedicated_bearer_header() -> None: + received: dict[str, object] = {} + + class FakeResponse: + status = 200 + + def __enter__(self) -> "FakeResponse": + return self + + def __exit__(self, *_args: object) -> None: + return None + + @staticmethod + def read() -> bytes: + return json.dumps({"ok": True}).encode("utf-8") + + def opener(request: object, **kwargs: object) -> FakeResponse: + received["request"] = request + received["kwargs"] = kwargs + return FakeResponse() + + result = read_console_confirmation( + confirmation_url="https://console.example/api/internal/reconciliation-recovery-confirmation", + recovery_id="ibkr-soxl-live-recovery", + token="controller-token", + opener=opener, + ) + + assert result == {"ok": True} + request = received["request"] + assert getattr(request, "get_header")("Authorization") == "Bearer controller-token" + assert "recovery_id=ibkr-soxl-live-recovery" in getattr(request, "full_url") From 9e27af3c133f9d49313475b499ef4e3b0aa7b2c4 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Mon, 31 Aug 2026 03:29:53 +0800 Subject: [PATCH 2/2] chore: pin strict recovery verification contract Co-Authored-By: Codex --- pyproject.toml | 4 ++-- ...est_reconciliation_recovery_verify_only.py | 23 +++++++++++++++++++ uv.lock | 6 ++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cbfe358..237d79c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "google-cloud-secret-manager", "google-cloud-storage", "yfinance", - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@1da621eea63018380383e29910af468278c99f69", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@5f1c5497f9e2bbd9bcbef5e5053fab49e6ad45ee", "us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@4584de176ca27955270471284663a13a2ce7f828", "hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@c526f2ed869c59f37a494d5fd3a64be299855ec8", ] @@ -64,5 +64,5 @@ include = [ [tool.uv] override-dependencies = [ - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@1da621eea63018380383e29910af468278c99f69", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@5f1c5497f9e2bbd9bcbef5e5053fab49e6ad45ee", ] diff --git a/tests/test_reconciliation_recovery_verify_only.py b/tests/test_reconciliation_recovery_verify_only.py index 6d2c153..8eae4b3 100644 --- a/tests/test_reconciliation_recovery_verify_only.py +++ b/tests/test_reconciliation_recovery_verify_only.py @@ -180,6 +180,29 @@ def test_verify_only_keeps_plan_closed_when_deployed_target_is_not_reconcile_onl assert "reconciliation_recovery_current_state_not_reconcile_only" in result["findings"] +def test_verify_only_rejects_a_current_receipt_with_confirmation_second_timestamp() -> None: + start = datetime(2026, 8, 31, 1, 0, tzinfo=timezone.utc) + candidate = _candidate_payload(start) + candidate_value = candidate["candidate"] + assert isinstance(candidate_value, dict) + candidate_sha256 = str(candidate_value["candidate_sha256"]) + confirmed_at = start + timedelta(minutes=3) + + result = verify_reconciliation_recovery( + candidate_payload=candidate, + dual_review_payload=_dual_review(candidate_sha256), + confirmation_payload=_console_response(candidate_sha256, confirmed_at=confirmed_at), + current_receipt_payload={"evidence": _evidence(observed_at=confirmed_at).to_dict()}, + runtime_target_payload=_runtime_target(), + recovery_id="ibkr-soxl-live-recovery", + now=start + timedelta(minutes=5), + ) + + assert result["ready_for_atomic_state_transition"] is False + assert result["transition_plan"] is None + assert "reconciliation_recovery_evidence_not_reobserved_after_confirmation" in result["findings"] + + def test_read_console_confirmation_uses_dedicated_bearer_header() -> None: received: dict[str, object] = {} diff --git a/uv.lock b/uv.lock index 45500cd..57226a6 100644 --- a/uv.lock +++ b/uv.lock @@ -17,7 +17,7 @@ resolution-markers = [ ] [manifest] -overrides = [{ name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=1da621eea63018380383e29910af468278c99f69" }] +overrides = [{ name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=5f1c5497f9e2bbd9bcbef5e5053fab49e6ad45ee" }] [[package]] name = "beautifulsoup4" @@ -791,7 +791,7 @@ requires-dist = [ { name = "pytest", marker = "extra == 'test'" }, { name = "pytest-cov", marker = "extra == 'test'" }, { name = "pytz" }, - { name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=1da621eea63018380383e29910af468278c99f69" }, + { name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=5f1c5497f9e2bbd9bcbef5e5053fab49e6ad45ee" }, { name = "requests" }, { name = "ruff", marker = "extra == 'test'" }, { name = "us-equity-strategies", git = "https://github.com/QuantStrategyLab/UsEquityStrategies.git?rev=4584de176ca27955270471284663a13a2ce7f828" }, @@ -1327,7 +1327,7 @@ wheels = [ [[package]] name = "quant-platform-kit" version = "0.10.0" -source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=1da621eea63018380383e29910af468278c99f69#1da621eea63018380383e29910af468278c99f69" } +source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=5f1c5497f9e2bbd9bcbef5e5053fab49e6ad45ee#5f1c5497f9e2bbd9bcbef5e5053fab49e6ad45ee" } [[package]] name = "requests"