@@ -187,6 +187,62 @@ def test_migrate_from_sqlalchemy_pickle(tmp_path):
187187 dest_session .close ()
188188
189189
190+ def test_migrate_from_sqlalchemy_pickle_preserves_safe_actions_pickle (tmp_path ):
191+ """Migration should preserve normal v0 EventActions pickle payloads."""
192+ source_db_path = tmp_path / "source_pickle_safe_actions.db"
193+ dest_db_path = tmp_path / "dest_json_safe_actions.db"
194+ source_db_url = f"sqlite:///{ source_db_path } "
195+ dest_db_url = f"sqlite:///{ dest_db_path } "
196+
197+ source_engine = create_engine (source_db_url )
198+ v0 .Base .metadata .create_all (source_engine )
199+ SourceSession = sessionmaker (bind = source_engine )
200+
201+ now = datetime .now (timezone .utc )
202+ with SourceSession () as source_session :
203+ source_session .add (
204+ v0 .StorageSession (
205+ app_name = "app1" ,
206+ user_id = "user1" ,
207+ id = "session1" ,
208+ state = {},
209+ create_time = now ,
210+ update_time = now ,
211+ )
212+ )
213+ source_session .commit ()
214+
215+ actions = EventActions (
216+ state_delta = {"skey" : "updated" },
217+ artifact_delta = {"artifact.txt" : 2 },
218+ )
219+ source_session .add (
220+ v0 .StorageEvent (
221+ id = "event1" ,
222+ app_name = "app1" ,
223+ user_id = "user1" ,
224+ session_id = "session1" ,
225+ invocation_id = "invoke1" ,
226+ author = "user" ,
227+ actions = actions ,
228+ timestamp = now ,
229+ )
230+ )
231+ source_session .commit ()
232+
233+ mfsp .migrate (source_db_url , dest_db_url )
234+
235+ dest_engine = create_engine (dest_db_url )
236+ DestSession = sessionmaker (bind = dest_engine )
237+ with DestSession () as dest_session :
238+ event_res = dest_session .query (v1 .StorageEvent ).first ()
239+ assert event_res is not None
240+ assert event_res .event_data ["actions" ]["state_delta" ] == {"skey" : "updated" }
241+ assert event_res .event_data ["actions" ]["artifact_delta" ] == {
242+ "artifact.txt" : 2
243+ }
244+
245+
190246def test_migrate_from_sqlalchemy_pickle_blocks_unsafe_actions_pickle (
191247 tmp_path , monkeypatch
192248):
@@ -219,6 +275,7 @@ def test_migrate_from_sqlalchemy_pickle_blocks_unsafe_actions_pickle(
219275 source_session .commit ()
220276
221277 class Evil :
278+
222279 def __reduce__ (self ):
223280 # This is intentionally non-destructive: it only sets an env var.
224281 return (
@@ -228,8 +285,10 @@ def __reduce__(self):
228285
229286 source_session .execute (
230287 text (
231- "INSERT INTO events (id, app_name, user_id, session_id, invocation_id, author, actions, timestamp) "
232- "VALUES (:id, :app_name, :user_id, :session_id, :invocation_id, :author, :actions, :timestamp)"
288+ "INSERT INTO events (id, app_name, user_id, session_id,"
289+ " invocation_id, author, actions, timestamp) VALUES (:id,"
290+ " :app_name, :user_id, :session_id, :invocation_id, :author,"
291+ " :actions, :timestamp)"
233292 ),
234293 {
235294 "id" : "event1" ,
0 commit comments