--- # Authoritative formal design for the realtime session lifecycle HIERARCHY: # how the per-machine coordinators (M1-M5) relate as one statechart. # # The five machines (respcoord/turncoord/conncoord/compactcoord/ttscoord) are # implemented as separate single-writer coordinators, but they are not # independent: M1 (connection) is the PARENT region, and its children must # terminate when it does. This spec models that relationship — the property no # single per-machine spec can express — without merging the Go code. # # Companion to: # - docs/design/realtime-state-machines.md (the map + invariants #8/#10) # - the per-machine specs (response_lifecycle / turn_lifecycle / conn_lifecycle # / compaction / tts_pipeline) which check each machine in isolation. # # Regions modeled here are M1's DIRECT children — the ones the connection # goroutine owns and tears down: # conn M1: 0 live, 1 torn # vad M2: 0 stopped, 1 running, 2 terminated (handleVAD goroutine joined) # resp M3: 0 idle, 1 active, 2 terminated (respcoord Terminated) # compaction M4: 0 idle, 1 running, 2 terminated (compactcoord Terminated) # M5 (TTS) is nested UNDER a response (each response owns its TTS pipeline), so # "resp terminated => tts closed" is an M3-internal relationship, not a direct # child of conn; it is covered by tts_pipeline.fizz + the response path. # # NOTE: FizzBee is pre-1.0. Validate the exact syntax/CLI against the version # pinned in formal-verification/README.md before trusting the gate. deadlock_detection: false --- role Session: action Init: self.conn = 0 self.vad = 0 self.resp = 0 self.compaction = 0 # Children may only START work while the connection is live: no goroutine is # spawned after teardown (no resurrection / no send-after-close). atomic action VadStart: if self.conn == 0 and self.vad == 0: self.vad = 1 atomic action VadStop: if self.conn == 0 and self.vad == 1: self.vad = 0 atomic action RespStart: if self.conn == 0 and self.resp != 2: self.resp = 1 atomic action RespFinish: if self.resp == 1: self.resp = 0 atomic action CompTrigger: if self.conn == 0 and self.compaction == 0: self.compaction = 1 atomic action CompFinish: if self.compaction == 1: self.compaction = 0 # Parent teardown drives EVERY child to its terminal state in one step: the # connection goroutine stops + joins the VAD goroutine (vad->2), shuts down # the response coordinator (resp->2), and cancels + joins the in-flight # compaction (compaction->2). THE RELATIONSHIP: a torn parent implies all # children terminal. # # To reproduce the real M4 gap (compaction left fire-and-forget, able to # outlive the session), delete `self.compaction = 2` below: the checker then # reports ChildrenDieWithParent violated (conn torn while compaction still # running). Likewise dropping vad/resp reproduces a leaked VAD/response. atomic action Teardown: if self.conn == 0: self.conn = 1 self.vad = 2 self.resp = 2 self.compaction = 2 action Init: s = Session() # SAFETY (the hierarchy invariant): once the connection is torn, every child is # terminal — no VAD goroutine, response, or compaction outlives the session # (Part 4, invariants #8/#10). The start guards above additionally make "no child # starts after teardown" unreachable. always assertion ChildrenDieWithParent: return s.conn == 0 or (s.vad == 2 and s.resp == 2 and s.compaction == 2)