fix: harden skipped ledger resolution paths
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
b88b21b772
commit
48dfc3e76f
@@ -60,6 +60,7 @@ from trex_core.runtime.config import (
|
||||
|
||||
_DEFAULT_TIMEOUT = 10.0
|
||||
_DEFAULT_LEDGER_BACKFILL_AUDIT_DIR = "artifacts/validation/ledger_backfill"
|
||||
_DEFAULT_LEDGER_SKIP_RESOLVE_AUDIT_DIR = "artifacts/validation/ledger_skip_resolve"
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -406,6 +407,24 @@ def _write_ledger_backfill_audit(args, report: dict) -> str:
|
||||
return str(path)
|
||||
|
||||
|
||||
def _ledger_skip_resolve_audit_path(args, skip_key: str) -> Path:
|
||||
base = Path(getattr(args, "audit_dir", _DEFAULT_LEDGER_SKIP_RESOLVE_AUDIT_DIR))
|
||||
stamp = datetime.now(tz=UTC).strftime("%Y%m%dT%H%M%SZ")
|
||||
name = (
|
||||
f"{stamp}--{_sanitize_path_component(_resolve_instance(args))}"
|
||||
f"--{_sanitize_path_component(skip_key)}.json"
|
||||
)
|
||||
return base.resolve() / name
|
||||
|
||||
|
||||
def _write_ledger_skip_resolve_audit(args, report: dict) -> str:
|
||||
path = _ledger_skip_resolve_audit_path(args, report["request"]["skip_key"])
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(_json.dumps(report, ensure_ascii=False, indent=2, default=str) + "\n",
|
||||
encoding="utf-8")
|
||||
return str(path)
|
||||
|
||||
|
||||
def _lookup_ledger_backfill_evidence(cfg: DaemonConfig, *, trading_date: str, execution_id: str):
|
||||
from trex_core.runtime.broker_provider import build_broker_provider
|
||||
|
||||
@@ -661,6 +680,14 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
)
|
||||
p_skips_resolve.add_argument("--skip-key", required=True)
|
||||
p_skips_resolve.add_argument("--reason", default="operator_reconciled")
|
||||
p_skips_resolve.add_argument("--yes", action="store_true", help="skip confirmation")
|
||||
p_skips_resolve.add_argument("--live", action="store_true",
|
||||
help="confirm a LIVE ledger skip mutation (with --yes)")
|
||||
p_skips_resolve.add_argument(
|
||||
"--audit-dir",
|
||||
default=_DEFAULT_LEDGER_SKIP_RESOLVE_AUDIT_DIR,
|
||||
help="directory for persistent JSON audit reports",
|
||||
)
|
||||
_add_common(p_skips_resolve)
|
||||
|
||||
return parser
|
||||
@@ -1057,13 +1084,61 @@ def _cmd_ledger_skips_list(args, cfg) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_ledger_skips_resolve(args, cfg) -> int:
|
||||
def _cmd_ledger_skips_resolve(args, cfg, confirm) -> int:
|
||||
from trex_core.adapters.persistence.ledger_skip_store import PostgreSQLLedgerSkipStore
|
||||
|
||||
instance = _resolve_instance(args)
|
||||
broker_name, is_live = _broker_mode(cfg)
|
||||
report = {
|
||||
"request": {
|
||||
"instance": instance,
|
||||
"broker": broker_name,
|
||||
"live_account": is_live,
|
||||
"skip_key": args.skip_key,
|
||||
"resolution_reason": args.reason,
|
||||
},
|
||||
"result": None,
|
||||
"record": None,
|
||||
"audit_path": None,
|
||||
}
|
||||
if is_live and not getattr(args, "live", False):
|
||||
report["result"] = {"status": "rejected", "reason": "live account requires --live (with --yes)"}
|
||||
report["audit_path"] = _write_ledger_skip_resolve_audit(args, report)
|
||||
_fail("live account requires --live (with --yes)", args.json)
|
||||
if args.json:
|
||||
_emit(report, as_json=True)
|
||||
else:
|
||||
_emit_pretty_json(report)
|
||||
return 2
|
||||
if not getattr(args, "yes", False):
|
||||
if args.json:
|
||||
report["result"] = {"status": "rejected", "reason": "--json ledger skips resolve requires --yes"}
|
||||
report["audit_path"] = _write_ledger_skip_resolve_audit(args, report)
|
||||
_fail("--json ledger skips resolve requires --yes", True)
|
||||
_emit(report, as_json=True)
|
||||
return 2
|
||||
prompt = (
|
||||
f"Resolve skipped ledger record on instance={instance} broker={broker_name}"
|
||||
f"{' LIVE' if is_live else ''}: {args.skip_key}?"
|
||||
)
|
||||
if not confirm(prompt):
|
||||
report["result"] = {"status": "aborted", "reason": "operator declined confirmation"}
|
||||
report["audit_path"] = _write_ledger_skip_resolve_audit(args, report)
|
||||
print("aborted")
|
||||
_emit_pretty_json(report)
|
||||
return 1
|
||||
store = PostgreSQLLedgerSkipStore(cfg.dsn)
|
||||
record = store.resolve_by_key(args.skip_key, resolution_reason=args.reason)
|
||||
record = store.resolve_by_key(
|
||||
args.skip_key,
|
||||
instance_key=instance,
|
||||
resolution_reason=args.reason,
|
||||
)
|
||||
if record is None:
|
||||
report["result"] = {"status": "not_found", "reason": f"unresolved ledger skip not found: {args.skip_key}"}
|
||||
report["audit_path"] = _write_ledger_skip_resolve_audit(args, report)
|
||||
_fail(f"unresolved ledger skip not found: {args.skip_key}", args.json)
|
||||
if args.json:
|
||||
_emit(report, as_json=True)
|
||||
return 1
|
||||
emitter, transport = _default_alarm_emitter(cfg)
|
||||
try:
|
||||
@@ -1071,8 +1146,11 @@ def _cmd_ledger_skips_resolve(args, cfg) -> int:
|
||||
finally:
|
||||
transport.close()
|
||||
payload = {"result": "resolved", "record": _render_ledger_skip_record(record)}
|
||||
report["result"] = {"status": "resolved"}
|
||||
report["record"] = payload["record"]
|
||||
report["audit_path"] = _write_ledger_skip_resolve_audit(args, report)
|
||||
if args.json:
|
||||
_emit(payload, as_json=True)
|
||||
_emit({**payload, "audit_path": report["audit_path"]}, as_json=True)
|
||||
else:
|
||||
print(f"resolved {record.skip_key} reason={record.resolution_reason}")
|
||||
return 0
|
||||
@@ -1338,7 +1416,7 @@ def main(argv: list[str] | None = None, *, rpc=None, confirm=None) -> int:
|
||||
and getattr(args, "ledger_cmd", None) == "skips"
|
||||
and getattr(args, "ledger_skips_cmd", None) == "resolve"
|
||||
):
|
||||
return _cmd_ledger_skips_resolve(args, cfg)
|
||||
return _cmd_ledger_skips_resolve(args, cfg, confirm)
|
||||
|
||||
# direct-DB control commands (pause/resume stay direct-DB, OQ-3).
|
||||
if args.cmd in ("pause", "resume", "paused"):
|
||||
|
||||
Reference in New Issue
Block a user