@@ -29,7 +29,9 @@ impl OwnedVersionedData for Actor {
2929 1 => Ok ( Self :: V1 ( serde_bare:: from_slice ( payload) ?) ) ,
3030 2 => Ok ( Self :: V2 ( serde_bare:: from_slice ( payload) ?) ) ,
3131 3 => Ok ( Self :: V3 ( serde_bare:: from_slice ( payload) ?) ) ,
32- 4 => Ok ( Self :: V4 ( serde_bare:: from_slice ( payload) ?) ) ,
32+ 4 => Ok ( Self :: V4 ( Self :: decode_v4_with_legacy_raw_args_fallback (
33+ payload,
34+ ) ?) ) ,
3335 _ => bail ! ( "invalid actor persist version: {version}" ) ,
3436 }
3537 }
@@ -54,6 +56,58 @@ impl OwnedVersionedData for Actor {
5456}
5557
5658impl Actor {
59+ fn decode_v4_with_legacy_raw_args_fallback ( payload : & [ u8 ] ) -> Result < v4:: Actor > {
60+ #[ derive( serde:: Deserialize ) ]
61+ struct LegacyRawV4Actor {
62+ input : Option < Vec < u8 > > ,
63+ has_initialized : bool ,
64+ state : Vec < u8 > ,
65+ scheduled_events : Vec < LegacyRawV4ScheduleEvent > ,
66+ }
67+
68+ #[ derive( serde:: Deserialize ) ]
69+ struct LegacyRawV4ScheduleEvent {
70+ event_id : String ,
71+ timestamp_ms : i64 ,
72+ action : String ,
73+ args : Vec < u8 > ,
74+ }
75+
76+ // serde_bare accepts any nonzero bool as true, so legacy raw args can
77+ // sometimes decode as current v4. Only accept canonical current-v4 bytes.
78+ let current_error = match serde_bare:: from_slice :: < v4:: Actor > ( payload) {
79+ Ok ( actor) => {
80+ if serde_bare:: to_vec ( & actor) . is_ok_and ( |encoded| encoded == payload) {
81+ return Ok ( actor) ;
82+ }
83+ None
84+ }
85+ Err ( error) => Some ( error) ,
86+ } ;
87+
88+ // A short-lived v4 writer stored schedule args as raw `data` while
89+ // the fixed v4 schema expects `optional<Cbor>`. Decode that reused
90+ // version so actors persisted by the bad writer can restart.
91+ match serde_bare:: from_slice :: < LegacyRawV4Actor > ( payload) {
92+ Ok ( actor) => Ok ( v4:: Actor {
93+ input : actor. input ,
94+ has_initialized : actor. has_initialized ,
95+ state : actor. state ,
96+ scheduled_events : actor
97+ . scheduled_events
98+ . into_iter ( )
99+ . map ( |event| v4:: ScheduleEvent {
100+ event_id : event. event_id ,
101+ timestamp : event. timestamp_ms ,
102+ action : event. action ,
103+ args : ( !event. args . is_empty ( ) ) . then_some ( event. args ) ,
104+ } )
105+ . collect ( ) ,
106+ } ) ,
107+ Err ( legacy_error) => Err ( current_error. unwrap_or ( legacy_error) . into ( ) ) ,
108+ }
109+ }
110+
57111 fn v1_to_v2 ( self ) -> Result < Self > {
58112 let Self :: V1 ( data) = self else {
59113 bail ! ( "expected actor persist v1 Actor" ) ;
0 commit comments