1111
1212import argparse
1313import json
14- from collections .abc import Iterable , Mapping
14+ from collections .abc import Iterable , Mapping , Sequence
1515from datetime import datetime
1616from pathlib import Path
1717from typing import Any
1818
19+ from application .broker_reconciliation_candidate import (
20+ SourceReceiptExpectation , calculate_source_receipts_sha256 , validate_reconciliation_candidate_sources ,
21+ )
22+
1923from quant_platform_kit .common .broker_reconciliation import BrokerReconciliationEvidence
2024from quant_platform_kit .common .broker_reconciliation_enrollment import (
2125 evaluate_broker_reconciliation_baseline_enrollment ,
@@ -48,19 +52,30 @@ def extract_reconciliation_evidence(payload: Mapping[str, Any]) -> BrokerReconci
4852def evaluate_receipts (
4953 payloads : Iterable [Mapping [str , Any ]],
5054 * ,
55+ source_receipt_records : Sequence [Mapping [str , object ]],
56+ expectations : Sequence [SourceReceiptExpectation ],
5157 now : datetime | None = None ,
5258) -> dict [str , object ]:
5359 """Return a redacted candidate or stable findings for a private controller."""
5460
5561 evidences = [extract_reconciliation_evidence (payload ) for payload in payloads ]
56- evaluation = evaluate_broker_reconciliation_baseline_enrollment (evidences , now = now )
62+ if not evidences :
63+ raise ValueError ("at least one reconciliation receipt is required" )
64+ root = calculate_source_receipts_sha256 (
65+ source_receipt_records , strategy_profile = evidences [0 ].strategy_profile , expectations = expectations ,
66+ )
67+ if sorted (record ["evidence_sha256" ] for record in source_receipt_records ) != sorted (item .evidence_sha256 for item in evidences ):
68+ raise ValueError ("source records must match receipt evidence members exactly" )
69+ evaluation = evaluate_broker_reconciliation_baseline_enrollment (evidences , now = now , source_receipts_sha256 = root )
5770 result : dict [str , object ] = {
5871 "schema_version" : "ibkr_reconciliation_baseline_enrollment.v1" ,
5972 "ready_for_independent_review" : evaluation .ready_for_independent_review ,
6073 "findings" : [finding .value for finding in evaluation .findings ],
6174 }
6275 if evaluation .candidate is not None :
63- result ["candidate" ] = evaluation .candidate .to_dict ()
76+ result ["candidate" ] = validate_reconciliation_candidate_sources (
77+ evaluation .candidate , source_receipt_records = source_receipt_records , expectations = expectations ,
78+ ).to_dict ()
6479 return result
6580
6681
@@ -76,6 +91,21 @@ def _load_payload(path: Path) -> dict[str, Any]:
7691 return value
7792
7893
94+ def load_source_inputs (
95+ records_path : Path , expectations_path : Path ,
96+ ) -> tuple [list [Mapping [str , object ]], tuple [SourceReceiptExpectation , ...]]:
97+ """Load existing private record lists, never infer trusted expectations."""
98+ try :
99+ records = json .loads (records_path .read_text (encoding = "utf-8" ))
100+ raw_expectations = json .loads (expectations_path .read_text (encoding = "utf-8" ))
101+ if not isinstance (records , list ) or not isinstance (raw_expectations , list ):
102+ raise ValueError ("source inputs must be lists" )
103+ expectations = tuple (SourceReceiptExpectation (** item ) for item in raw_expectations )
104+ except (OSError , TypeError , ValueError ) as exc :
105+ raise ValueError ("private source inputs are invalid or unavailable" ) from exc
106+ return records , expectations
107+
108+
79109def main (argv : list [str ] | None = None ) -> int :
80110 parser = argparse .ArgumentParser (
81111 description = "Create a non-authorising legacy IBKR reconciliation baseline candidate."
@@ -85,15 +115,17 @@ def main(argv: list[str] | None = None) -> int:
85115 action = "append" ,
86116 type = Path ,
87117 required = True ,
88- help = "Private /reconcile response or persisted runtime report; supply at least twice ." ,
118+ help = "Private /reconcile response or persisted runtime report; supply at least once ." ,
89119 )
120+ parser .add_argument ("--source-records" , type = Path , required = True , help = "Private saved source record list" )
121+ parser .add_argument ("--source-expectations" , type = Path , required = True , help = "Independently verified private source expectations" )
90122 parser .add_argument ("--now" , help = "Optional ISO-8601 time used for deterministic validation." )
91123 args = parser .parse_args (argv )
92- if len (args .receipt ) < 2 :
93- parser .error ("--receipt must be supplied at least twice" )
94124 try :
95125 reference_now = datetime .fromisoformat (args .now .replace ("Z" , "+00:00" )) if args .now else None
96- result = evaluate_receipts ((_load_payload (path ) for path in args .receipt ), now = reference_now )
126+ records , expectations = load_source_inputs (args .source_records , args .source_expectations )
127+ result = evaluate_receipts ((_load_payload (path ) for path in args .receipt ), now = reference_now ,
128+ source_receipt_records = records , expectations = expectations )
97129 except ValueError as exc :
98130 parser .error (str (exc ))
99131 print (json .dumps (result , ensure_ascii = False , sort_keys = True ))
0 commit comments