okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
57a82be
main
1"""Checked JSON messages at the kernel and remote connection boundaries.""" 2 3from typing import Literal ,NotRequired ,TypedDict ,cast 4 5 6class Execute (TypedDict ): 7type :Literal ["execute" ] 8id :str 9code :str 10durable :NotRequired [bool ] 11max_edge :NotRequired [int ] 12timeout_ms :NotRequired [int ] 13 14 15class HostSuccess (TypedDict ): 16ok :Literal [True ] 17value :object 18 19 20class HostFailure (TypedDict ): 21ok :Literal [False ] 22code :str 23message :str 24 25 26HostReply = HostSuccess | HostFailure 27 28 29class Reply (TypedDict ): 30type :Literal ["reply" ] 31id :str 32value :HostReply 33 34 35class Interrupt (TypedDict ): 36type :Literal ["interrupt" ] 37id :str 38# `memory` is the kernel's own, from its memory guard; the owner sends the others. 39reason :NotRequired [Literal ["deadline" ,"cancelled" ,"memory" ]] 40 41 42class Shutdown (TypedDict ): 43type :Literal ["shutdown" ] 44 45 46Invoke = TypedDict ( 47"Invoke" , 48 { 49"type" :Literal ["invoke" ], 50"id" :str , 51"name" :NotRequired [str ], 52"target" :NotRequired [dict [str ,str ]], 53"args" :NotRequired [list [object ]], 54"kwargs" :NotRequired [dict [str ,object ]], 55"await" :NotRequired [bool ], 56 }, 57) 58 59 60class Introspect (TypedDict ): 61type :Literal ["introspect" ] 62id :str 63 64 65class Release (TypedDict ): 66type :Literal ["release" ] 67handle :str 68 69 70class State (TypedDict ): 71"""Save the namespace to disk, or revive it from an earlier save.""" 72 73type :Literal ["snapshot" ,"restore" ] 74id :str 75path :str 76 77 78class JobStop (TypedDict ): 79"""Stop a background job.""" 80 81type :Literal ["job_stop" ] 82id :str 83 84 85Incoming = ( 86Execute 87| Reply 88| Interrupt 89| Shutdown 90| Invoke 91| Introspect 92| Release 93| State 94| JobStop 95) 96 97 98class SavedCell (TypedDict ): 99id :str 100source :str 101started :bool 102parent :str | None 103finished :bool 104 105 106class Ready (TypedDict ): 107type :Literal ["ready" ] 108pid :int 109pgid :int | None 110leader :str | None 111 112 113class StartupError (TypedDict ): 114type :Literal ["startup_error" ] 115message :str 116 117 118class RemoteCall (TypedDict ): 119type :Literal ["call" ] 120id :str 121method :str 122args :dict [str ,object ] 123 124 125class InvocationError (TypedDict ): 126ename :str 127evalue :str 128traceback :list [str ] 129 130 131class MirrorState (TypedDict ,total = False ): 132job :str | None 133seen :int 134tail :str 135tail_bytes :int 136exit_code :int | None 137timed_out :bool 138duration :float | None 139 140 141class Invoked (TypedDict ): 142type :Literal ["invoked" ] 143id :str 144ok :bool 145value :NotRequired [object ] 146handle :NotRequired [str ] 147state :NotRequired [MirrorState ] 148error :NotRequired [InvocationError ] 149cancelled :NotRequired [bool ] 150 151 152class RemoteHandle (TypedDict ): 153id :str 154repr :str 155 156 157class Introspected (TypedDict ): 158type :Literal ["introspected" ] 159id :str 160names :list [str ] 161handles :list [RemoteHandle ] 162 163 164class Mirror (MirrorState ): 165type :Literal ["mirror" ] 166handle :str 167 168 169class RemoteEvent (TypedDict ): 170type :Literal ["job_start" ,"job" ,"trace" ,"done" ] 171id :str 172service :NotRequired [bool ]# job_start: nothing waits on the job's end 173 174 175class RemoteCleanup (TypedDict ): 176type :Literal ["cleanup" ] 177module :str 178 179 180class JobStopReply (TypedDict ): 181type :Literal ["job_stop" ] 182id :str 183ok :bool 184error :NotRequired [str ] 185 186 187RemoteMessage = ( 188Ready 189| StartupError 190| RemoteCall 191| Invoked 192| Introspected 193| Mirror 194| RemoteEvent 195| RemoteCleanup 196| JobStopReply 197) 198 199 200def _object (value :object ,context :str )-> dict [str ,object ]: 201if not isinstance (value ,dict )or not all (isinstance (key ,str )for key in value ): 202raise ValueError (f" { context } : expected an object" ) 203return cast (dict [str ,object ],value ) 204 205 206def _field ( 207message :dict [str ,object ], 208field :str , 209expected :type | tuple [type , ...], 210context :str , 211* , 212optional :bool = False , 213)-> None : 214if optional and field not in message : 215return 216if field not in message : 217raise ValueError (f" { context } . { field } : required field" ) 218value = message [field ] 219if not isinstance (value ,expected )or ( 220isinstance (value ,bool ) 221and expected is not bool 222and (expected is int or isinstance (expected ,tuple )and int in expected ) 223 ): 224raise ValueError (f" { context } . { field } : invalid field type" ) 225 226 227def _envelope (value :object )-> tuple [dict [str ,object ],str ]: 228message = _object (value ,"message" ) 229_field (message ,"type" ,str ,"message" ) 230return message ,cast (str ,message ["type" ]) 231 232 233def parse_incoming (value :object )-> Incoming : 234"""Validate a kernel request before it reaches the event loop.""" 235message ,kind = _envelope (value ) 236if kind not in { 237"execute" , 238"reply" , 239"interrupt" , 240"shutdown" , 241"invoke" , 242"introspect" , 243"release" , 244"snapshot" , 245"restore" , 246"job_stop" , 247 }: 248raise ValueError (f" { kind } .type: unknown message type" ) 249if kind not in {"shutdown" ,"release" }: 250_field (message ,"id" ,str ,kind ) 251if kind == "execute" : 252_field (message ,"code" ,str ,kind ) 253_field (message ,"durable" ,bool ,kind ,optional = True ) 254_field (message ,"max_edge" ,int ,kind ,optional = True ) 255_field (message ,"timeout_ms" ,int ,kind ,optional = True ) 256elif kind == "reply" : 257reply = _object (message .get ("value" ),"reply.value" ) 258_field (reply ,"ok" ,bool ,"reply.value" ) 259if reply ["ok" ]: 260_field (reply ,"value" ,object ,"reply.value" ) 261else : 262_field (reply ,"code" ,str ,"reply.value" ) 263_field (reply ,"message" ,str ,"reply.value" ) 264elif kind == "interrupt" : 265_field (message ,"reason" ,str ,kind ,optional = True ) 266if "reason" in message and message ["reason" ]not in ( 267"deadline" , 268"cancelled" , 269"memory" , 270 ): 271raise ValueError ("interrupt.reason: expected deadline, cancelled or memory" ) 272elif kind == "invoke" : 273for field ,expected in ( 274 ("name" ,str ), 275 ("args" ,list ), 276 ("kwargs" ,dict ), 277 ("await" ,bool ), 278 ): 279_field (message ,field ,expected ,kind ,optional = True ) 280if "kwargs" in message : 281_object (message ["kwargs" ],"invoke.kwargs" ) 282if "target" in message : 283target = _object (message ["target" ],"invoke.target" ) 284if set (target )not in ({"handle" }, {"pending" }): 285raise ValueError ( 286"invoke.target: expected exactly one handle or pending" 287 ) 288_field (target ,next (iter (target )),str ,"invoke.target" ) 289elif kind == "release" : 290_field (message ,"handle" ,str ,kind ) 291elif kind in {"snapshot" ,"restore" }: 292_field (message ,"path" ,str ,kind ) 293return cast (Incoming ,message ) 294 295 296def _mirror (value :object ,context :str )-> None : 297state = _object (value ,context ) 298for field ,expected in ( 299 ("job" , (str ,type (None ))), 300 ("seen" ,int ), 301 ("tail" ,str ), 302 ("tail_bytes" ,int ), 303 ("exit_code" , (int ,type (None ))), 304 ("timed_out" ,bool ), 305 ("duration" , (int ,float ,type (None ))), 306 ): 307_field (state ,field ,expected ,context ,optional = True ) 308 309 310def parse_remote (value :object )-> RemoteMessage : 311"""Validate remote replies and the event fields the owner consumes.""" 312message ,kind = _envelope (value ) 313if kind in {"invoked" ,"introspected" ,"call" ,"job_start" ,"job" ,"trace" ,"done" }: 314_field (message ,"id" ,str ,kind ) 315if kind == "ready" : 316_field (message ,"pid" ,int ,kind ) 317_field (message ,"pgid" , (int ,type (None )),kind ) 318_field (message ,"leader" , (str ,type (None )),kind ) 319elif kind == "startup_error" : 320_field (message ,"message" ,str ,kind ) 321elif kind == "call" : 322_field (message ,"method" ,str ,kind ) 323_object (message .get ("args" ),"call.args" ) 324elif kind == "invoked" : 325_field (message ,"ok" ,bool ,kind ) 326_field (message ,"handle" ,str ,kind ,optional = True ) 327_field (message ,"cancelled" ,bool ,kind ,optional = True ) 328if "state" in message : 329_mirror (message ["state" ],"invoked.state" ) 330if "error" in message or not message ["ok" ]: 331error = _object (message .get ("error" ),"invoked.error" ) 332_field (error ,"ename" ,str ,"invoked.error" ) 333_field (error ,"evalue" ,str ,"invoked.error" ) 334_field (error ,"traceback" ,list ,"invoked.error" ) 335if not all ( 336isinstance (line ,str )for line in cast (list [object ],error ["traceback" ]) 337 ): 338raise ValueError ("invoked.error.traceback: expected strings" ) 339if message ["ok" ]and "handle" not in message and "value" not in message : 340raise ValueError ("invoked.value: required without handle" ) 341elif kind == "introspected" : 342_field (message ,"names" ,list ,kind ) 343if not all ( 344isinstance (name ,str )for name in cast (list [object ],message ["names" ]) 345 ): 346raise ValueError ("introspected.names: expected strings" ) 347_field (message ,"handles" ,list ,kind ) 348for item in cast (list [object ],message ["handles" ]): 349handle = _object (item ,"introspected.handles" ) 350_field (handle ,"id" ,str ,"introspected.handles" ) 351_field (handle ,"repr" ,str ,"introspected.handles" ) 352elif kind == "mirror" : 353_field (message ,"handle" ,str ,kind ) 354_mirror (message ,kind ) 355elif kind == "cleanup" : 356_field (message ,"module" ,str ,kind ) 357elif kind == "job_stop" : 358_field (message ,"ok" ,bool ,kind ) 359_field (message ,"error" ,str ,kind ,optional = True ) 360elif kind not in {"job_start" ,"job" ,"trace" ,"done" }: 361raise ValueError (f" { kind } .type: unknown message type" ) 362return cast (RemoteMessage ,message )