Files
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

85 lines
3.6 KiB
Plaintext

---
# 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)