Documentation
¶
Overview ¶
Package gsm implements governed state machines: finite state machines whose states live in a registry that enforces invariants via compensation. Events are applied in any order; the registry guarantees convergence to the same valid state regardless of ordering.
Example ¶
Example demonstrates the complete workflow: define state variables, invariants, and events, then verify convergence guarantees at build time. Runtime event application is O(1) table lookup with zero overhead.
package main
import (
"fmt"
"github.com/blackwell-systems/gsm"
)
func main() {
b := gsm.NewRegistry("counter")
// State variable with bounded domain
count := b.Int("count", 0, 10)
// Business rule: count cannot exceed maximum
b.Invariant("cap_at_10").
Watches(count).
Holds(func(s gsm.State) bool {
return s.GetInt(count) <= 10
}).
Repair(func(s gsm.State) gsm.State {
return s.SetInt(count, 10) // Clamp to maximum
}).
Add()
// Event: increment counter
b.Event("increment").
Writes(count).
Apply(func(s gsm.State) gsm.State {
return s.SetInt(count, s.GetInt(count)+1)
}).
Add()
// Build with verification
machine, report, err := b.Build()
if err != nil {
panic(fmt.Sprintf("convergence not guaranteed: %v\n%s", err, report))
}
fmt.Printf("Convergence: %v\n", report.WFC && report.CC)
// Runtime usage - increment beyond limit
s := machine.NewState()
for i := 0; i < 15; i++ {
s = machine.Apply(s, "increment")
}
fmt.Printf("Count: %d\n", s.GetInt(count))
}
Output: Convergence: true Count: 10
Index ¶
- type CCFailure
- type CheckFunc
- type EffectFunc
- type EventBuilder
- type InvariantBuilder
- type Machine
- type Registry
- func (r *Registry) Bool(name string) Var
- func (r *Registry) Build() (*Machine, *Report, error)
- func (r *Registry) Enum(name string, values ...string) Var
- func (r *Registry) Event(name string) *EventBuilder
- func (r *Registry) Independent(e1name, e2name string) *Registry
- func (r *Registry) Int(name string, min, max int) Var
- func (r *Registry) Invariant(name string) *InvariantBuilder
- func (r *Registry) OnlyDeclaredPairs() *Registry
- type Report
- type State
- type Var
- type VarKind
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CCFailure ¶
type CCFailure struct {
Event1 string
Event2 string
State State
Result1 State // apply e1 then e2
Result2 State // apply e2 then e1
}
CCFailure describes a specific CC violation.
type EventBuilder ¶
type EventBuilder struct {
// contains filtered or unexported fields
}
EventBuilder provides a fluent API for declaring an event.
func (*EventBuilder) Apply ¶
func (eb *EventBuilder) Apply(fn EffectFunc) *EventBuilder
Apply sets the event's effect function.
func (*EventBuilder) Guard ¶
func (eb *EventBuilder) Guard(fn CheckFunc) *EventBuilder
Guard sets an optional precondition. If the guard returns false, the event is a no-op in that state.
func (*EventBuilder) Writes ¶
func (eb *EventBuilder) Writes(vars ...Var) *EventBuilder
Writes declares which variables this event modifies.
type InvariantBuilder ¶
type InvariantBuilder struct {
// contains filtered or unexported fields
}
InvariantBuilder provides a fluent API for declaring an invariant.
func (*InvariantBuilder) Add ¶
func (ib *InvariantBuilder) Add()
Add registers the invariant with the registry.
func (*InvariantBuilder) Holds ¶ added in v0.1.2
func (ib *InvariantBuilder) Holds(fn CheckFunc) *InvariantBuilder
Holds sets the invariant predicate. Returns true if the invariant holds.
func (*InvariantBuilder) Repair ¶
func (ib *InvariantBuilder) Repair(fn EffectFunc) *InvariantBuilder
Repair sets the compensation function. Called when Check returns false. Must only modify variables declared in Over().
func (*InvariantBuilder) Watches ¶ added in v0.1.2
func (ib *InvariantBuilder) Watches(vars ...Var) *InvariantBuilder
Watches declares the invariant's footprint — which variables it constrains and which its repair may modify.
type Machine ¶
type Machine struct {
// contains filtered or unexported fields
}
Machine is an immutable, verified governed state machine. Created by Builder.Build() after WFC and CC verification passes. All operations are table lookups — no computation at runtime.
func (*Machine) Apply ¶
Apply processes an event, returning the unique normal form. This is a single table lookup — O(1). Panics if the event name is unknown.
Example ¶
ExampleMachine_Apply demonstrates O(1) event application at runtime. All compensation is precomputed during Build() - no runtime overhead.
package main
import (
"fmt"
"github.com/blackwell-systems/gsm"
)
func main() {
b := gsm.NewRegistry("light")
// State machine: off -> on -> off
power := b.Bool("power")
b.Event("toggle").
Writes(power).
Apply(func(s gsm.State) gsm.State {
return s.SetBool(power, !s.GetBool(power))
}).
Add()
machine, _, err := b.Build()
if err != nil {
panic(err)
}
s := machine.NewState()
fmt.Printf("Initial: power=%v\n", s.GetBool(power))
s = machine.Apply(s, "toggle")
fmt.Printf("After toggle: power=%v\n", s.GetBool(power))
s = machine.Apply(s, "toggle")
fmt.Printf("After toggle: power=%v\n", s.GetBool(power))
}
Output: Initial: power=false After toggle: power=true After toggle: power=false
func (*Machine) Export ¶
Export writes the verified machine to a portable JSON format. The exported file can be loaded by runtime implementations in any language, enabling O(1) event application without reimplementing verification.
The format contains:
- State variable definitions (types, domains)
- Event names (ordered)
- Normal form table: nf[stateID] → normalized stateID
- Step table: step[eventID][stateID] → normalized result stateID
- Verification metadata (WFC/CC results, state count, etc.)
Runtime libraries only need to:
- Load the JSON
- Implement Apply(state, event) as step[events[event]][state]
Example runtime (Python):
import json
class Machine:
def __init__(self, path):
with open(path) as f:
d = json.load(f)
self.events = {n: i for i, n in enumerate(d['events'])}
self.step = d['step']
def apply(self, state, event):
return self.step[self.events[event]][state]
Example ¶
ExampleMachine_Export demonstrates JSON export for multi-language runtimes.
package main
import (
"fmt"
"os"
"runtime"
"github.com/blackwell-systems/gsm"
)
func main() {
b := gsm.NewRegistry("simple")
state := b.Bool("state")
b.Event("toggle").
Writes(state).
Apply(func(s gsm.State) gsm.State {
return s.SetBool(state, !s.GetBool(state))
}).
Add()
machine, _, err := b.Build()
if err != nil {
panic(err)
}
// Export to JSON (verification tables + metadata)
tmpDir := "/tmp"
if runtime.GOOS == "windows" {
tmpDir = os.Getenv("TEMP")
}
path := fmt.Sprintf("%s/simple.json", tmpDir)
err = machine.Export(path)
if err != nil {
panic(err)
}
fmt.Println("Machine exported successfully")
}
Output: Machine exported successfully
type Registry ¶ added in v0.1.3
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the rules that govern state machines: variables, invariants, compensation functions, and events. After declaring these, call Build() to verify convergence properties and produce an immutable Machine.
The registry is the central authority that defines what states are valid and how to repair invalid states through compensation.
func NewRegistry ¶ added in v0.1.3
NewRegistry creates a Registry for a named state machine. By default, all event pairs are checked for CC. Use Independent() to restrict checking to specific pairs.
func (*Registry) Build ¶ added in v0.1.3
Build verifies WFC and CC, then returns an immutable Machine.
Example ¶
ExampleRegistry_Build shows the verification process and report output. The registry exhaustively enumerates the state space and verifies WFC (well-founded compensation) and CC (compensation commutativity).
package main
import (
"fmt"
"github.com/blackwell-systems/gsm"
)
func main() {
b := gsm.NewRegistry("counter")
// Single integer variable
count := b.Int("count", 0, 10)
// Invariant: count must stay <= 10
b.Invariant("cap_at_10").
Watches(count).
Holds(func(s gsm.State) bool {
return s.GetInt(count) <= 10
}).
Repair(func(s gsm.State) gsm.State {
return s.SetInt(count, 10) // Clamp to max
}).
Add()
// Increment event
b.Event("increment").
Writes(count).
Apply(func(s gsm.State) gsm.State {
return s.SetInt(count, s.GetInt(count)+1)
}).
Add()
machine, report, err := b.Build()
if err != nil {
panic(err)
}
fmt.Printf("States verified: %d\n", report.StateCount)
fmt.Printf("WFC: %v\n", report.WFC)
fmt.Printf("CC: %v\n", report.CC)
// Test the machine
s := machine.NewState()
for i := 0; i < 15; i++ {
s = machine.Apply(s, "increment")
}
fmt.Printf("Count after 15 increments: %d\n", s.GetInt(count))
}
Output: States verified: 11 WFC: true CC: true Count after 15 increments: 10
func (*Registry) Event ¶ added in v0.1.3
func (r *Registry) Event(name string) *EventBuilder
Event begins declaring a named event.
func (*Registry) Independent ¶ added in v0.1.3
Independent declares that two events may arrive in either order (they are not causally related). Compensation Commutativity (CC) will be checked for this pair.
Calling Independent() automatically switches to declared-only mode: only explicitly declared pairs will be verified. This avoids checking all O(n²) event pairs when most are causally ordered.
func (*Registry) Invariant ¶ added in v0.1.3
func (r *Registry) Invariant(name string) *InvariantBuilder
Invariant begins declaring a named invariant.
Example ¶
ExampleRegistry_Invariant demonstrates the priority-ordered compensation system. When multiple invariants are violated, repairs fire in declaration order.
package main
import (
"fmt"
"github.com/blackwell-systems/gsm"
)
func main() {
b := gsm.NewRegistry("stock")
qty := b.Int("qty", 0, 100)
reserved := b.Int("reserved", 0, 100)
// Invariant 1: reserved cannot exceed quantity (higher priority)
b.Invariant("reserved_lte_qty").
Watches(qty, reserved).
Holds(func(s gsm.State) bool {
return s.GetInt(reserved) <= s.GetInt(qty)
}).
Repair(func(s gsm.State) gsm.State {
return s.SetInt(reserved, s.GetInt(qty))
}).
Add()
// Invariant 2: quantity cannot be negative (lower priority)
b.Invariant("qty_gte_zero").
Watches(qty).
Holds(func(s gsm.State) bool {
return s.GetInt(qty) >= 0
}).
Repair(func(s gsm.State) gsm.State {
return s.SetInt(qty, 0)
}).
Add()
b.Event("reduce").
Writes(qty).
Apply(func(s gsm.State) gsm.State {
return s.SetInt(qty, s.GetInt(qty)-10)
}).
Add()
machine, _, err := b.Build()
if err != nil {
panic(err)
}
s := machine.NewState()
s = s.SetInt(qty, 5)
s = s.SetInt(reserved, 3)
s = machine.Apply(s, "reduce") // qty becomes -5, triggers compensation
fmt.Printf("qty=%d, reserved=%d\n", s.GetInt(qty), s.GetInt(reserved))
}
Output: qty=0, reserved=0
func (*Registry) OnlyDeclaredPairs ¶ added in v0.1.3
OnlyDeclaredPairs explicitly switches Compensation Commutativity (CC) checking to only the event pairs declared via Independent(). This is now automatic when you call Independent(), but this method remains for explicitness and backward compatibility.
type Report ¶
type Report struct {
Name string
StateCount int
VarCount int
EventCount int
// WFC results
WFC bool
MaxRepairLen int // longest compensation chain
// CC results
CC bool
PairsTotal int
PairsDisjoint int // proved by footprint disjointness
PairsBrute int // proved by exhaustive check
CCFailure *CCFailure // non-nil if CC failed
}
Report contains the results of build-time verification.
type State ¶
type State struct {
// contains filtered or unexported fields
}
State is a compact, immutable representation of all variable values. Internally it is a bitpacked uint64, enabling use as a table index for precomputed normal forms.
func (State) SetInt ¶
SetInt returns a new State with an int variable set. Value is clamped to the variable's declared range.
func (State) String ¶
String returns a human-readable representation.
Example ¶
ExampleState_String shows the human-readable state representation.
package main
import (
"fmt"
"github.com/blackwell-systems/gsm"
)
func main() {
b := gsm.NewRegistry("example")
status := b.Enum("status", "pending", "active", "done")
count := b.Int("count", 0, 100)
enabled := b.Bool("enabled")
machine, _, err := b.Build()
if err != nil {
panic(err)
}
s := machine.NewState()
s = s.Set(status, "active")
s = s.SetInt(count, 42)
s = s.SetBool(enabled, true)
fmt.Println(s)
}
Output: {status=active, count=42, enabled=true}