1- """Rollback manager — monitors post-deployment performance and auto-rolls back on degradation."""
1+ """Rollback monitor — records no-order rollback proposals on degradation."""
22
33from __future__ import annotations
44
55from collections .abc import Mapping
6- from datetime import datetime , timezone
76from typing import Any
87
98import numpy as np
1312from quant_platform_kit .strategy_lifecycle .performance_store import PerformanceStore
1413from quant_platform_kit .strategy_lifecycle .update_policy import UpdatePolicy
1514
16-
17- def _now_iso () -> str :
18- return datetime .now (timezone .utc ).isoformat ()
19-
20-
2115class RollbackManager :
22- """Monitors post-update performance and triggers rollback if needed.
16+ """Monitors post-update performance and records rollback proposals.
17+
18+ This class has no deployment, runtime-target, broker, or order adapter.
19+ A performance breach is therefore an auditable proposal, not an executed
20+ rollback. A platform-specific, owner-authorized control path must provide
21+ any actual rollback separately.
2322
2423 Usage::
2524
2625 mgr = RollbackManager(store=store, policy=policy)
2726 decision = mgr.evaluate("global_etf_rotation", domain="us_equity")
2827 if decision["should_rollback"]:
29- mgr.rollback (...)
28+ mgr.propose_rollback (...)
3029 """
3130
3231 def __init__ (
@@ -62,16 +61,25 @@ def evaluate(
6261 deployed_max_dd: Max drawdown at time of deployment.
6362
6463 Returns:
65- Dict with "should_rollback", "reason", "live_sharpe", "live_max_dd".
64+ Dict with a rollback recommendation and an explicit
65+ ``rollback_execution_authorized=False`` boundary.
6666 """
6767 # Get latest live performance
6868 latest_snapshot = self ._store .load_latest_snapshot (domain , strategy_profile )
6969 if latest_snapshot is None :
70- return {"should_rollback" : False , "reason" : "No live performance data available" }
70+ return {
71+ "should_rollback" : False ,
72+ "reason" : "No live performance data available" ,
73+ "rollback_execution_authorized" : False ,
74+ }
7175
7276 ref_window = latest_snapshot .windows .get (126 ) or latest_snapshot .windows .get (252 )
7377 if ref_window is None :
74- return {"should_rollback" : False , "reason" : "No window metrics available" }
78+ return {
79+ "should_rollback" : False ,
80+ "reason" : "No window metrics available" ,
81+ "rollback_execution_authorized" : False ,
82+ }
7583
7684 live_sharpe = ref_window .sharpe_ratio
7785 live_max_dd = ref_window .max_drawdown
@@ -102,9 +110,10 @@ def evaluate(
102110 "reason" : "; " .join (reasons ) if reasons else "Performance within acceptable range" ,
103111 "live_sharpe" : live_sharpe ,
104112 "live_max_dd" : live_max_dd ,
113+ "rollback_execution_authorized" : False ,
105114 }
106115
107- def rollback (
116+ def propose_rollback (
108117 self ,
109118 strategy_profile : str ,
110119 * ,
@@ -113,27 +122,43 @@ def rollback(
113122 param_version_to : int ,
114123 params_before : Mapping [str , Any ],
115124 params_after : Mapping [str , Any ],
116- reason : str = "Auto-rollback due to post-deployment performance degradation" ,
125+ reason : str = "Rollback proposal due to post-deployment performance degradation" ,
117126 ) -> dict [str , Any ]:
118- """Execute a rollback and record it in the audit log ."""
127+ """Record a rollback proposal without changing any external state ."""
119128 entry = record_audit_entry (
120129 strategy_profile = strategy_profile ,
121130 domain = domain ,
122- stage = UpdateStage .ROLLED_BACK ,
123- operator = "auto_optimizer " ,
131+ stage = UpdateStage .ROLLBACK_PROPOSED ,
132+ operator = "rollback_monitor " ,
124133 param_version_from = param_version_from ,
125134 param_version_to = param_version_to ,
126135 params_before = params_before ,
127136 params_after = params_after ,
128137 reason = reason ,
129- approval_source = "auto" ,
138+ approval_source = "not_authorized" ,
139+ store = self ._store ,
130140 )
131141
132142 return {
133- "rolled_back" : True ,
143+ "proposal_recorded" : True ,
144+ "rolled_back" : False ,
145+ "rollback_executed" : False ,
146+ "execution_authorized" : False ,
147+ "requires_owner_approval" : True ,
148+ "stage" : UpdateStage .ROLLBACK_PROPOSED .value ,
134149 "strategy_profile" : strategy_profile ,
135150 "from_version" : param_version_from ,
136151 "to_version" : param_version_to ,
137152 "entry_id" : entry .entry_id ,
138153 "reason" : reason ,
139154 }
155+
156+ def rollback (self , * args : Any , ** kwargs : Any ) -> dict [str , Any ]:
157+ """Compatibility alias for :meth:`propose_rollback`.
158+
159+ Kept so callers do not fail at import time, but it never claims or
160+ performs an external rollback. Consumers must check
161+ ``rollback_executed`` rather than treating an audit record as runtime
162+ evidence.
163+ """
164+ return self .propose_rollback (* args , ** kwargs )
0 commit comments