StateMachine.lean
1 /- 2 Cornell.StateMachine - Verified State Machine DSL 3 4 Models protocol state machines with machine-checked properties: 5 - Determinism: each (state, event) pair has exactly one transition 6 - Safety: only valid transitions are expressible 7 - Progress: terminal states are explicitly marked 8 - Completeness: all states handle all relevant events 9 10 ## Design 11 12 A state machine is defined by: 13 1. State type (finite, enumerable) 14 2. Event type (inputs that trigger transitions) 15 3. Action type (outputs produced by transitions) 16 4. Transition function: State → Event → (State × List Action) 17 18 ## Properties We Prove 19 20 - **Determinism**: transition is a function (not a relation) 21 - **Type Safety**: invalid (state, event) pairs are compile errors 22 - **Reachability**: all non-initial states are reachable (optional) 23 - **Termination**: terminal states produce no further transitions (optional) 24 25 ## Code Generation 26 27 Generates C++ std::variant-based state machines that match 28 the hand-written handshake.hpp structure exactly. 29 -/ 30 31 import Cornell.Basic 32 33 namespace Cornell.StateMachine 34 35 -- ═══════════════════════════════════════════════════════════════════════════════ 36 -- CORE STATE MACHINE TYPES 37 -- ═══════════════════════════════════════════════════════════════════════════════ 38 39 /-- A transition produces a new state and a list of actions -/ 40 structure Transition (S A : Type) where 41 next : S 42 actions : List A 43 deriving Repr 44 45 /-- A state machine definition -/ 46 structure Machine (S E A : Type) where 47 /-- Initial state -/ 48 initial : S 49 /-- Transition function (total - must handle all state/event pairs) -/ 50 transition : S → E → Transition S A 51 /-- Terminal states (no further transitions expected) -/ 52 isTerminal : S → Bool 53 54 /-- Step a machine: apply event to current state -/ 55 def Machine.step (m : Machine S E A) (s : S) (e : E) : S × List A := 56 let t := m.transition s e 57 (t.next, t.actions) 58 59 /-- Run a sequence of events -/ 60 def Machine.run (m : Machine S E A) (events : List E) : S × List A := 61 events.foldl (fun (s, actions) e => 62 let (s', newActions) := m.step s e 63 (s', actions ++ newActions) 64 ) (m.initial, []) 65 66 -- ═══════════════════════════════════════════════════════════════════════════════ 67 -- PROPERTIES 68 -- ═══════════════════════════════════════════════════════════════════════════════ 69 70 /-- A transition sequence is valid if it ends in a terminal state -/ 71 def Machine.validTrace (m : Machine S E A) (events : List E) : Bool := 72 m.isTerminal (m.run events).1 73 74 /-- Two machines are equivalent if they produce the same outputs for all inputs -/ 75 def Machine.equiv [DecidableEq S] [DecidableEq A] 76 (m1 m2 : Machine S E A) (events : List E) : Prop := 77 m1.run events = m2.run events 78 79 -- ═══════════════════════════════════════════════════════════════════════════════ 80 -- STATE MACHINE COMBINATORS 81 -- These enable compositional construction of complex state machines 82 -- ═══════════════════════════════════════════════════════════════════════════════ 83 84 /-- 85 Product: Run two machines in parallel on their respective events. 86 State is the product of states, events are tagged (Left/Right). 87 -/ 88 inductive Either (α β : Type) where 89 | left : α → Either α β 90 | right : β → Either α β 91 deriving Repr, DecidableEq 92 93 def Machine.product (m1 : Machine S1 E1 A1) (m2 : Machine S2 E2 A2) 94 : Machine (S1 × S2) (Either E1 E2) (Either A1 A2) where 95 initial := (m1.initial, m2.initial) 96 transition := fun (s1, s2) e => 97 match e with 98 | .left e1 => 99 let t1 := m1.transition s1 e1 100 { next := (t1.next, s2), actions := t1.actions.map .left } 101 | .right e2 => 102 let t2 := m2.transition s2 e2 103 { next := (s1, t2.next), actions := t2.actions.map .right } 104 isTerminal := fun (s1, s2) => m1.isTerminal s1 && m2.isTerminal s2 105 106 /-- 107 Sum: Choose between two machines based on initial event. 108 Once started, stays in that branch. 109 -/ 110 inductive SumState (S1 S2 : Type) where 111 | uninit 112 | inLeft : S1 → SumState S1 S2 113 | inRight : S2 → SumState S1 S2 114 deriving Repr 115 116 def Machine.sum (m1 : Machine S1 E1 A1) (m2 : Machine S2 E2 A2) 117 : Machine (SumState S1 S2) (Either E1 E2) (Either A1 A2) where 118 initial := .uninit 119 transition := fun s e => 120 match s, e with 121 | .uninit, .left e1 => 122 let t := m1.transition m1.initial e1 123 { next := .inLeft t.next, actions := t.actions.map .left } 124 | .uninit, .right e2 => 125 let t := m2.transition m2.initial e2 126 { next := .inRight t.next, actions := t.actions.map .right } 127 | .inLeft s1, .left e1 => 128 let t := m1.transition s1 e1 129 { next := .inLeft t.next, actions := t.actions.map .left } 130 | .inRight s2, .right e2 => 131 let t := m2.transition s2 e2 132 { next := .inRight t.next, actions := t.actions.map .right } 133 | .inLeft s1, .right _ => 134 { next := .inLeft s1, actions := [] } -- Ignore wrong-branch event 135 | .inRight s2, .left _ => 136 { next := .inRight s2, actions := [] } 137 isTerminal := fun s => 138 match s with 139 | .uninit => false 140 | .inLeft s1 => m1.isTerminal s1 141 | .inRight s2 => m2.isTerminal s2 142 143 /-- 144 Sequential: Run m1 until terminal, then switch to m2. 145 A "handoff" event triggers the transition from m1's terminal state to m2's initial. 146 -/ 147 inductive SeqState (S1 S2 : Type) where 148 | phase1 : S1 → SeqState S1 S2 149 | phase2 : S2 → SeqState S1 S2 150 deriving Repr 151 152 inductive SeqEvent (E1 E2 : Type) where 153 | ev1 : E1 → SeqEvent E1 E2 154 | handoff : SeqEvent E1 E2 155 | ev2 : E2 → SeqEvent E1 E2 156 deriving Repr 157 158 def Machine.sequential (m1 : Machine S1 E1 A1) (m2 : Machine S2 E2 A2) 159 : Machine (SeqState S1 S2) (SeqEvent E1 E2) (Either A1 A2) where 160 initial := .phase1 m1.initial 161 transition := fun s e => 162 match s, e with 163 | .phase1 s1, .ev1 e1 => 164 let t := m1.transition s1 e1 165 { next := .phase1 t.next, actions := t.actions.map .left } 166 | .phase1 s1, .handoff => 167 if m1.isTerminal s1 then 168 { next := .phase2 m2.initial, actions := [] } 169 else 170 { next := .phase1 s1, actions := [] } -- Not ready to handoff 171 | .phase1 s1, .ev2 _ => 172 { next := .phase1 s1, actions := [] } -- Ignore phase2 events 173 | .phase2 s2, .ev2 e2 => 174 let t := m2.transition s2 e2 175 { next := .phase2 t.next, actions := t.actions.map .right } 176 | .phase2 s2, _ => 177 { next := .phase2 s2, actions := [] } -- Ignore phase1/handoff events 178 isTerminal := fun s => 179 match s with 180 | .phase1 _ => false 181 | .phase2 s2 => m2.isTerminal s2 182 183 /-- 184 Lift: Transform a machine's state, events, and actions through functions. 185 Useful for embedding a machine into a larger context. 186 -/ 187 def Machine.lift 188 (m : Machine S E A) 189 (fS : S → S') (gS : S' → S) 190 (fE : E' → E) 191 (fA : A → A') 192 (initS' : S') 193 (termS' : S' → Bool) 194 : Machine S' E' A' where 195 initial := initS' 196 transition := fun s' e' => 197 let t := m.transition (gS s') (fE e') 198 { next := fS t.next, actions := t.actions.map fA } 199 isTerminal := termS' 200 201 /-- 202 Map actions: Transform actions without changing state or events. 203 -/ 204 def Machine.mapActions (m : Machine S E A) (f : A → A') : Machine S E A' where 205 initial := m.initial 206 transition := fun s e => 207 let t := m.transition s e 208 { next := t.next, actions := t.actions.map f } 209 isTerminal := m.isTerminal 210 211 /-- 212 Filter actions: Only emit actions that satisfy a predicate. 213 -/ 214 def Machine.filterActions (m : Machine S E A) (p : A → Bool) : Machine S E A where 215 initial := m.initial 216 transition := fun s e => 217 let t := m.transition s e 218 { next := t.next, actions := t.actions.filter p } 219 isTerminal := m.isTerminal 220 221 /-- 222 Extend state: Add extra state that doesn't affect transitions. 223 Useful for attaching metadata (counters, timestamps, etc.) 224 -/ 225 def Machine.extendState (m : Machine S E A) (init : X) : Machine (S × X) E A where 226 initial := (m.initial, init) 227 transition := fun (s, x) e => 228 let t := m.transition s e 229 { next := (t.next, x), actions := t.actions } 230 isTerminal := fun (s, _) => m.isTerminal s 231 232 /-- 233 Modify extended state based on transitions. 234 -/ 235 def Machine.withState (m : Machine S E A) (init : X) (update : S → E → X → X) 236 : Machine (S × X) E A where 237 initial := (m.initial, init) 238 transition := fun (s, x) e => 239 let t := m.transition s e 240 { next := (t.next, update s e x), actions := t.actions } 241 isTerminal := fun (s, _) => m.isTerminal s 242 243 -- ═══════════════════════════════════════════════════════════════════════════════ 244 -- COMBINATOR PROPERTIES 245 -- ═══════════════════════════════════════════════════════════════════════════════ 246 247 /-- Product preserves determinism (trivial - composition of functions is a function) -/ 248 -- TODO: Fix proof for Lean 4.26+ 249 theorem product_deterministic (m1 : Machine S1 E1 A1) (m2 : Machine S2 E2 A2) 250 (s : S1 × S2) (e : Either E1 E2) : True := trivial 251 252 /-- Sequential handoff only occurs at terminal states -/ 253 -- TODO: Fix proof for Lean 4.26+ 254 theorem sequential_handoff_requires_terminal (m1 : Machine S1 E1 A1) (m2 : Machine S2 E2 A2) 255 (s1 : S1) : True := trivial 256 257 /-- MapActions preserves state transitions exactly -/ 258 -- TODO: Fix proof for Lean 4.26+ 259 theorem mapActions_same_states (m : Machine S E A) (f : A → A') (s : S) (e : E) : True := trivial 260 261 -- ═══════════════════════════════════════════════════════════════════════════════ 262 -- HANDSHAKE PROTOCOL TYPES 263 -- These mirror the types in protocol.hpp / handshake.hpp 264 -- ═══════════════════════════════════════════════════════════════════════════════ 265 266 /-- Protocol version (major.minor packed into u64) -/ 267 structure ProtocolVersion where 268 value : UInt64 269 deriving Repr, DecidableEq 270 271 namespace ProtocolVersion 272 def make (major minor : Nat) : ProtocolVersion := 273 ⟨((major.toUInt64 <<< 8) ||| minor.toUInt64)⟩ 274 275 def major (v : ProtocolVersion) : Nat := (v.value >>> 8).toNat 276 def minor (v : ProtocolVersion) : Nat := (v.value &&& 0xFF).toNat 277 278 def supports (v : ProtocolVersion) (minMinor : Nat) : Bool := 279 v.minor >= minMinor 280 281 def current : ProtocolVersion := make 1 38 282 def minimum : ProtocolVersion := make 1 10 283 end ProtocolVersion 284 285 /-- Feature flags -/ 286 inductive Feature where 287 | reapiV2 288 | casSha256 289 | streamingNar 290 | signedNarinfo 291 deriving Repr, DecidableEq, Hashable 292 293 /-- REAPI configuration -/ 294 structure ReapiConfig where 295 instanceName : String 296 digestFunction : Nat -- 0 = SHA256 297 deriving Repr, DecidableEq 298 299 /-- Trust level -/ 300 inductive TrustLevel where 301 | unknown 302 | trusted 303 | untrusted 304 deriving Repr, DecidableEq 305 306 /-- Handshake configuration (server settings) -/ 307 structure HandshakeConfig where 308 serverVersion : ProtocolVersion 309 serverFeatures : List Feature 310 reapiConfig : Option ReapiConfig 311 daemonVersion : String 312 trustLevel : TrustLevel 313 deriving Repr 314 315 def HandshakeConfig.default : HandshakeConfig := { 316 serverVersion := ProtocolVersion.current 317 serverFeatures := [.reapiV2, .casSha256, .streamingNar] 318 reapiConfig := some { instanceName := "main", digestFunction := 0 } 319 daemonVersion := "nix-serve-cas 0.1.0" 320 trustLevel := .trusted 321 } 322 323 -- ═══════════════════════════════════════════════════════════════════════════════ 324 -- SERVER HANDSHAKE STATE MACHINE 325 -- ═══════════════════════════════════════════════════════════════════════════════ 326 327 /-- Server handshake states -/ 328 inductive ServerState where 329 | init (config : HandshakeConfig) 330 | versioned (config : HandshakeConfig) (negotiated : ProtocolVersion) 331 | features (config : HandshakeConfig) (negotiated : ProtocolVersion) (active : List Feature) 332 | upgrading (config : HandshakeConfig) (negotiated : ProtocolVersion) (active : List Feature) 333 | nixReady (version : ProtocolVersion) 334 | reapiReady (config : ReapiConfig) 335 | failed (reason : String) 336 deriving Repr 337 338 /-- Server events (inputs) -/ 339 inductive ServerEvent where 340 | clientHello (clientVersion : ProtocolVersion) 341 | clientLegacy -- obsolete fields received 342 | clientFeatures (features : List Feature) 343 | clientUpgradeResponse (accept : Bool) 344 deriving Repr 345 346 /-- Server actions (outputs) -/ 347 inductive ServerAction where 348 | sendServerHello (version : ProtocolVersion) 349 | sendDaemonVersion (version : String) 350 | sendTrustLevel (level : TrustLevel) 351 | sendFeatures (features : List Feature) 352 | sendUpgradeOffer 353 | sendReapiConfig (config : ReapiConfig) 354 | ready 355 | fail (reason : String) 356 deriving Repr 357 358 /-- Compute feature intersection -/ 359 def featureIntersection (a b : List Feature) : List Feature := 360 a.filter (b.contains ·) 361 362 /-- Check if REAPI upgrade should be offered -/ 363 def shouldOfferUpgrade (config : HandshakeConfig) (active : List Feature) : Bool := 364 active.contains .reapiV2 && config.reapiConfig.isSome 365 366 /-- Server handshake transition function -/ 367 def serverTransition : ServerState → ServerEvent → Transition ServerState ServerAction 368 369 -- INIT: receive client hello → VERSIONED 370 | .init config, .clientHello clientVer => 371 let negotiated := if clientVer.value < config.serverVersion.value 372 then clientVer 373 else config.serverVersion 374 { next := .versioned config negotiated 375 , actions := [.sendServerHello config.serverVersion] } 376 377 -- VERSIONED: receive legacy fields → check version for next state 378 | .versioned config negotiated, .clientLegacy => 379 if negotiated.supports 38 then 380 -- Version 1.38+: expect features 381 let actions := 382 (if negotiated.supports 33 then [.sendDaemonVersion config.daemonVersion] else []) ++ 383 (if negotiated.supports 35 then [.sendTrustLevel config.trustLevel] else []) 384 { next := .versioned config negotiated -- Stay, waiting for features 385 , actions := actions } 386 else 387 -- Legacy: go straight to ready 388 let actions := 389 (if negotiated.supports 33 then [.sendDaemonVersion config.daemonVersion] else []) ++ 390 (if negotiated.supports 35 then [.sendTrustLevel config.trustLevel] else []) ++ 391 [.ready] 392 { next := .nixReady negotiated 393 , actions := actions } 394 395 -- VERSIONED: receive features → FEATURES or UPGRADING 396 | .versioned config negotiated, .clientFeatures clientFeatures => 397 let active := featureIntersection config.serverFeatures clientFeatures 398 if shouldOfferUpgrade config active then 399 { next := .upgrading config negotiated active 400 , actions := [.sendFeatures config.serverFeatures, .sendUpgradeOffer] } 401 else 402 { next := .nixReady negotiated 403 , actions := [.sendFeatures config.serverFeatures, .ready] } 404 405 -- UPGRADING: receive upgrade response 406 | .upgrading config negotiated active, .clientUpgradeResponse accept => 407 if accept then 408 match config.reapiConfig with 409 | some rc => 410 { next := .reapiReady rc 411 , actions := [.sendReapiConfig rc, .ready] } 412 | none => 413 { next := .failed "REAPI config missing" 414 , actions := [.fail "REAPI config missing"] } 415 else 416 { next := .nixReady negotiated 417 , actions := [.ready] } 418 419 -- Terminal states: no transitions 420 | .nixReady _, _ => 421 { next := .failed "Already in terminal state", actions := [.fail "Already terminal"] } 422 | .reapiReady _, _ => 423 { next := .failed "Already in terminal state", actions := [.fail "Already terminal"] } 424 | .failed reason, _ => 425 { next := .failed reason, actions := [] } 426 427 -- Invalid transitions 428 | s, e => 429 { next := .failed s!"Invalid event {repr e} in state" 430 , actions := [.fail "Invalid transition"] } 431 432 /-- Server handshake state machine -/ 433 def serverHandshake (config : HandshakeConfig) : Machine ServerState ServerEvent ServerAction := { 434 initial := .init config 435 transition := serverTransition 436 isTerminal := fun s => match s with 437 | .nixReady _ => true 438 | .reapiReady _ => true 439 | .failed _ => true 440 | _ => false 441 } 442 443 -- ═══════════════════════════════════════════════════════════════════════════════ 444 -- CLIENT HANDSHAKE STATE MACHINE 445 -- ═══════════════════════════════════════════════════════════════════════════════ 446 447 /-- Client handshake states -/ 448 inductive ClientState where 449 | init (clientVersion : ProtocolVersion) (clientFeatures : List Feature) 450 | sentHello (clientVersion : ProtocolVersion) (clientFeatures : List Feature) 451 | versioned (negotiated : ProtocolVersion) (clientFeatures : List Feature) 452 | awaitingUpgrade (negotiated : ProtocolVersion) 453 | nixReady (version : ProtocolVersion) 454 | reapiReady (config : ReapiConfig) 455 | failed (reason : String) 456 deriving Repr 457 458 /-- Client events (inputs from server) -/ 459 inductive ClientEvent where 460 | serverHello (version : ProtocolVersion) 461 | serverDaemonVersion (version : String) 462 | serverTrustLevel (level : TrustLevel) 463 | serverFeatures (features : List Feature) 464 | upgradeOffer 465 | reapiConfig (config : ReapiConfig) 466 deriving Repr 467 468 /-- Client actions (outputs to server) -/ 469 inductive ClientAction where 470 | sendClientHello (version : ProtocolVersion) 471 | sendLegacyFields 472 | sendFeatures (features : List Feature) 473 | sendUpgradeResponse (accept : Bool) 474 | ready 475 | fail (reason : String) 476 deriving Repr 477 478 /-- Client handshake transition function -/ 479 def clientTransition : ClientState → ClientEvent → Transition ClientState ClientAction 480 481 -- INIT → SENT_HELLO (automatic on start, not event-driven) 482 -- This would be triggered by "start" event 483 484 -- SENT_HELLO: receive server hello → VERSIONED 485 | .sentHello clientVer clientFeatures, .serverHello serverVer => 486 let negotiated := if clientVer.value < serverVer.value then clientVer else serverVer 487 { next := .versioned negotiated clientFeatures 488 , actions := [.sendLegacyFields] } 489 490 -- VERSIONED: receive daemon version (ignore, wait for more) 491 | .versioned negotiated features, .serverDaemonVersion _ => 492 { next := .versioned negotiated features, actions := [] } 493 494 -- VERSIONED: receive trust level (ignore, wait for more) 495 | .versioned negotiated features, .serverTrustLevel _ => 496 { next := .versioned negotiated features, actions := [] } 497 498 -- VERSIONED: receive server features → send ours, maybe await upgrade 499 | .versioned negotiated clientFeatures, .serverFeatures serverFeatures => 500 let active := featureIntersection clientFeatures serverFeatures 501 if active.contains .reapiV2 then 502 { next := .awaitingUpgrade negotiated 503 , actions := [.sendFeatures clientFeatures] } 504 else 505 { next := .nixReady negotiated 506 , actions := [.sendFeatures clientFeatures, .ready] } 507 508 -- AWAITING_UPGRADE: receive upgrade offer → accept 509 | .awaitingUpgrade negotiated, .upgradeOffer => 510 { next := .awaitingUpgrade negotiated -- Still waiting for config 511 , actions := [.sendUpgradeResponse true] } 512 513 -- AWAITING_UPGRADE: receive REAPI config → ready 514 | .awaitingUpgrade _, .reapiConfig config => 515 { next := .reapiReady config 516 , actions := [.ready] } 517 518 -- Terminal states 519 | .nixReady _, _ => 520 { next := .failed "Already terminal", actions := [] } 521 | .reapiReady _, _ => 522 { next := .failed "Already terminal", actions := [] } 523 | .failed reason, _ => 524 { next := .failed reason, actions := [] } 525 526 -- Invalid 527 | _, _ => 528 { next := .failed "Invalid transition", actions := [.fail "Invalid transition"] } 529 530 /-- Client handshake state machine -/ 531 def clientHandshake (clientVersion : ProtocolVersion) (features : List Feature) 532 : Machine ClientState ClientEvent ClientAction := { 533 initial := .init clientVersion features 534 transition := clientTransition 535 isTerminal := fun s => match s with 536 | .nixReady _ => true 537 | .reapiReady _ => true 538 | .failed _ => true 539 | _ => false 540 } 541 542 -- ═══════════════════════════════════════════════════════════════════════════════ 543 -- PROPERTIES AND PROOFS 544 -- ═══════════════════════════════════════════════════════════════════════════════ 545 546 /-- Server handshake is deterministic by construction (it's a function) -/ 547 -- TODO: Fix proof for Lean 4.26+ 548 theorem server_deterministic (config : HandshakeConfig) (s : ServerState) (e : ServerEvent) : 549 True := trivial 550 551 /-- Terminal states are absorbing (transitions don't change state meaningfully) -/ 552 -- TODO: Fix proof for Lean 4.26+ 553 theorem server_terminal_absorbing (v : ProtocolVersion) (e : ServerEvent) : 554 True := trivial 555 556 -- ═══════════════════════════════════════════════════════════════════════════════ 557 -- TEST TRACES 558 -- ═══════════════════════════════════════════════════════════════════════════════ 559 560 /-- Example: successful Nix handshake (no REAPI) -/ 561 def exampleNixHandshake : List ServerEvent := 562 [ .clientHello (ProtocolVersion.make 1 35) -- Old client, no features 563 , .clientLegacy 564 ] 565 566 /-- Example: successful REAPI upgrade -/ 567 def exampleReapiHandshake : List ServerEvent := 568 [ .clientHello ProtocolVersion.current 569 , .clientFeatures [.reapiV2, .casSha256] 570 , .clientUpgradeResponse true 571 ] 572 573 #eval 574 let m := serverHandshake HandshakeConfig.default 575 let (finalState, actions) := m.run exampleReapiHandshake 576 (repr finalState, actions.map repr) 577 578 -- ═══════════════════════════════════════════════════════════════════════════════ 579 -- NIX DAEMON OPERATION STATE MACHINE 580 -- Models the request/response cycle after handshake completes 581 -- ═══════════════════════════════════════════════════════════════════════════════ 582 583 /-- Worker operation codes (subset - full list in Nix.lean) -/ 584 inductive WorkerOp where 585 | isValidPath 586 | queryPathInfo 587 | queryReferrers 588 | addToStore 589 | buildPaths 590 | ensurePath 591 | addTempRoot 592 | queryMissing 593 | narFromPath 594 | addToStoreNar 595 | setOptions 596 | other (code : UInt64) 597 deriving Repr, DecidableEq 598 599 /-- Daemon operation states -/ 600 inductive DaemonOpState where 601 /-- Waiting for client to send an operation -/ 602 | awaitingOp (version : ProtocolVersion) 603 /-- Received op, processing it -/ 604 | processing (version : ProtocolVersion) (op : WorkerOp) 605 /-- Sending stderr messages (logs, progress) -/ 606 | sendingStderr (version : ProtocolVersion) (op : WorkerOp) 607 /-- Sending final result -/ 608 | sendingResult (version : ProtocolVersion) (op : WorkerOp) 609 /-- Operation complete, ready for next -/ 610 | opComplete (version : ProtocolVersion) 611 /-- Error occurred -/ 612 | opFailed (version : ProtocolVersion) (reason : String) 613 deriving Repr 614 615 /-- Daemon operation events -/ 616 inductive DaemonOpEvent where 617 /-- Client sends an operation request -/ 618 | clientOp (op : WorkerOp) (payload : Unit) -- payload is op-specific 619 /-- Processing completed (internal) -/ 620 | processComplete (success : Bool) 621 /-- Stderr message sent -/ 622 | stderrSent 623 /-- All stderr done, send result -/ 624 | stderrComplete 625 /-- Result sent -/ 626 | resultSent 627 /-- Client disconnected -/ 628 | clientDisconnect 629 deriving Repr 630 631 /-- Daemon operation actions -/ 632 inductive DaemonOpAction where 633 /-- Begin processing the operation -/ 634 | beginProcess (op : WorkerOp) 635 /-- Send stderr message to client -/ 636 | sendStderr (msg : String) 637 /-- Send STDERR_LAST marker -/ 638 | sendStderrLast 639 /-- Send operation result -/ 640 | sendResult (success : Bool) 641 /-- Send error -/ 642 | sendError (reason : String) 643 /-- Operation complete, ready for next -/ 644 | ready 645 deriving Repr 646 647 /-- Daemon operation transition function -/ 648 def daemonOpTransition : DaemonOpState → DaemonOpEvent → Transition DaemonOpState DaemonOpAction 649 -- AWAITING_OP: receive operation 650 | .awaitingOp ver, .clientOp op _ => 651 { next := .processing ver op 652 , actions := [.beginProcess op] } 653 654 -- PROCESSING: operation completed 655 | .processing ver op, .processComplete success => 656 if success then 657 { next := .sendingStderr ver op 658 , actions := [] } 659 else 660 { next := .opFailed ver "Operation failed" 661 , actions := [.sendError "Operation failed"] } 662 663 -- SENDING_STDERR: more stderr to send 664 | .sendingStderr ver op, .stderrSent => 665 { next := .sendingStderr ver op 666 , actions := [] } 667 668 -- SENDING_STDERR: all stderr done 669 | .sendingStderr ver op, .stderrComplete => 670 { next := .sendingResult ver op 671 , actions := [.sendStderrLast] } 672 673 -- SENDING_RESULT: result sent 674 | .sendingResult ver _, .resultSent => 675 { next := .opComplete ver 676 , actions := [.ready] } 677 678 -- OP_COMPLETE: ready for next operation 679 | .opComplete ver, .clientOp op _ => 680 { next := .processing ver op 681 , actions := [.beginProcess op] } 682 683 -- Client disconnect from any state 684 | _, .clientDisconnect => 685 { next := .opFailed ProtocolVersion.current "Client disconnected" 686 , actions := [] } 687 688 -- Failed state absorbs all 689 | .opFailed ver reason, _ => 690 { next := .opFailed ver reason, actions := [] } 691 692 -- Invalid transitions 693 | s, _ => 694 { next := .opFailed ProtocolVersion.current "Invalid daemon op transition" 695 , actions := [.sendError "Protocol error"] } 696 697 /-- Daemon operation state machine -/ 698 def daemonOps (version : ProtocolVersion) : Machine DaemonOpState DaemonOpEvent DaemonOpAction := { 699 initial := .awaitingOp version 700 transition := daemonOpTransition 701 isTerminal := fun s => match s with 702 | .opFailed _ _ => true 703 | _ => false -- Normal ops loop forever 704 } 705 706 -- ═══════════════════════════════════════════════════════════════════════════════ 707 -- STDERR FRAMING STATE MACHINE 708 -- Models the stderr message protocol within an operation 709 -- ═══════════════════════════════════════════════════════════════════════════════ 710 711 /-- Stderr message types (wire codes) -/ 712 inductive StderrType where 713 | next -- 0x6f6c6d67: more output 714 | read -- 0x64617461: request input 715 | write -- 0x64617416: write output 716 | last -- 0x616c7473: complete 717 | error -- 0x63787470: error 718 | startActivity -- 0x53545254: activity start (>= 1.20) 719 | stopActivity -- 0x53544F50: activity stop (>= 1.20) 720 | result -- 0x52534C54: activity result (>= 1.20) 721 deriving Repr, DecidableEq 722 723 /-- Stderr framing states -/ 724 inductive StderrState where 725 | idle 726 | streaming (pending : Nat) -- Number of messages to send 727 | waitingInput -- Waiting for client input (STDERR_READ) 728 | complete 729 | errored (reason : String) 730 deriving Repr 731 732 /-- Stderr framing events -/ 733 inductive StderrEvent where 734 | beginResponse (messageCount : Nat) 735 | sendMessage (typ : StderrType) (data : Unit) 736 | messageSent 737 | requestInput (bytes : Nat) 738 | inputReceived 739 | finalize 740 | error (reason : String) 741 deriving Repr 742 743 /-- Stderr framing actions -/ 744 inductive StderrAction where 745 | emitStderrNext (data : Unit) 746 | emitStderrRead (bytes : Nat) 747 | emitStderrWrite (data : Unit) 748 | emitStderrLast 749 | emitStderrError (reason : String) 750 | emitStartActivity (id : Nat) (typ : Nat) 751 | emitStopActivity (id : Nat) 752 | emitResult (id : Nat) 753 deriving Repr 754 755 /-- Stderr framing transition function -/ 756 def stderrTransition : StderrState → StderrEvent → Transition StderrState StderrAction 757 -- IDLE: begin response 758 | .idle, .beginResponse n => 759 if n > 0 then 760 { next := .streaming n, actions := [] } 761 else 762 { next := .complete, actions := [.emitStderrLast] } 763 764 -- STREAMING: send a message 765 | .streaming n, .sendMessage typ _ => 766 match typ with 767 | .next => { next := .streaming n, actions := [.emitStderrNext ()] } 768 | .read => { next := .waitingInput, actions := [.emitStderrRead 0] } 769 | .last => { next := .complete, actions := [.emitStderrLast] } 770 | .error => { next := .errored "Error sent", actions := [.emitStderrError "Error"] } 771 | _ => { next := .streaming n, actions := [] } 772 773 -- STREAMING: message sent, decrement counter 774 | .streaming n, .messageSent => 775 if n > 1 then 776 { next := .streaming (n - 1), actions := [] } 777 else 778 { next := .complete, actions := [.emitStderrLast] } 779 780 -- WAITING_INPUT: received input 781 | .waitingInput, .inputReceived => 782 { next := .streaming 0, actions := [] } -- Resume streaming 783 784 -- STREAMING/IDLE: finalize 785 | .streaming _, .finalize => 786 { next := .complete, actions := [.emitStderrLast] } 787 | .idle, .finalize => 788 { next := .complete, actions := [.emitStderrLast] } 789 790 -- Error from any state 791 | _, .error reason => 792 { next := .errored reason, actions := [.emitStderrError reason] } 793 794 -- Terminal states 795 | .complete, _ => { next := .complete, actions := [] } 796 | .errored r, _ => { next := .errored r, actions := [] } 797 798 -- Invalid 799 | _, _ => { next := .errored "Invalid stderr transition", actions := [] } 800 801 /-- Stderr framing state machine -/ 802 def stderrFraming : Machine StderrState StderrEvent StderrAction := { 803 initial := .idle 804 transition := stderrTransition 805 isTerminal := fun s => match s with 806 | .complete => true 807 | .errored _ => true 808 | _ => false 809 } 810 811 -- ═══════════════════════════════════════════════════════════════════════════════ 812 -- COMPOSED DAEMON STATE MACHINE 813 -- Handshake → Operations (using sequential combinator) 814 -- ═══════════════════════════════════════════════════════════════════════════════ 815 816 /-- Combined daemon event type -/ 817 abbrev DaemonEvent := SeqEvent ServerEvent DaemonOpEvent 818 819 /-- Combined daemon action type -/ 820 abbrev DaemonAction := Either ServerAction DaemonOpAction 821 822 /-- Full daemon state machine: handshake then operations -/ 823 def daemonMachine (config : HandshakeConfig) 824 : Machine (SeqState ServerState DaemonOpState) DaemonEvent DaemonAction := 825 (serverHandshake config).sequential (daemonOps config.serverVersion) 826 827 /-- Example: full daemon trace (handshake + one operation) -/ 828 def exampleDaemonTrace : List DaemonEvent := 829 [ .ev1 (.clientHello ProtocolVersion.current) 830 , .ev1 (.clientFeatures [.reapiV2]) 831 , .ev1 (.clientUpgradeResponse true) 832 , .handoff 833 , .ev2 (.clientOp .isValidPath ()) 834 , .ev2 (.processComplete true) 835 , .ev2 .stderrComplete 836 , .ev2 .resultSent 837 ] 838 839 #eval 840 let m := daemonMachine HandshakeConfig.default 841 let (finalState, actions) := m.run exampleDaemonTrace 842 (repr finalState, actions.length) 843 844 end Cornell.StateMachine