gomcp/internal/infrastructure/formalspec/formalspec.go

262 lines
7.3 KiB
Go

// Package formalspec implements SEC-012 TLA+ Formal Verification.
//
// Provides a Go representation of the Event Bus pipeline and
// Decision Logger chain specifications for formal verification.
//
// The TLA+ specifications can be model-checked with the TLC checker:
// tlc EventBusPipeline.tla
// tlc DecisionLoggerChain.tla
//
// This package provides:
// - Go types mirroring the TLA+ state machine
// - Invariant checking functions
// - Trace generation for debugging
package formalspec
import (
"fmt"
"log/slog"
"sync"
"time"
)
// --- Event Bus Pipeline Specification ---
// PipelineState represents the Event Bus pipeline state machine.
type PipelineState string
const (
StateInit PipelineState = "INIT"
StateScanning PipelineState = "SCANNING" // Secret Scanner (Step 0)
StateDedup PipelineState = "DEDUP" // Deduplication
StateCorrelate PipelineState = "CORRELATE" // Correlation Engine
StatePersist PipelineState = "PERSIST" // SQLite Persist
StateDecisionLog PipelineState = "DECISION_LOG" // Audit Decision Logger
StateComplete PipelineState = "COMPLETE"
StateError PipelineState = "ERROR"
)
// Transition represents a state transition in the pipeline.
type Transition struct {
From PipelineState `json:"from"`
To PipelineState `json:"to"`
Guard string `json:"guard"` // Condition for transition
Action string `json:"action"` // Side effect
Timestamp time.Time `json:"timestamp"`
}
// PipelineSpec defines all valid transitions in the Event Bus pipeline.
var PipelineSpec = []Transition{
{From: StateInit, To: StateScanning, Guard: "event_received", Action: "run_secret_scanner"},
{From: StateScanning, To: StateDedup, Guard: "no_secrets_found", Action: "dedup_check"},
{From: StateScanning, To: StateError, Guard: "secret_detected", Action: "reject_event"},
{From: StateDedup, To: StateCorrelate, Guard: "not_duplicate", Action: "run_correlation"},
{From: StateDedup, To: StateComplete, Guard: "is_duplicate", Action: "skip"},
{From: StateCorrelate, To: StatePersist, Guard: "correlation_done", Action: "persist_event"},
{From: StatePersist, To: StateDecisionLog, Guard: "persisted", Action: "log_decision"},
{From: StateDecisionLog, To: StateComplete, Guard: "logged", Action: "emit_complete"},
}
// PipelineInvariant defines a safety property that must always hold.
type PipelineInvariant struct {
Name string
Description string
Check func(state PipelineState, history []Transition) bool
}
// PipelineInvariants are the safety properties of the Event Bus.
var PipelineInvariants = []PipelineInvariant{
{
Name: "SecretScannerAlwaysFirst",
Description: "Secret Scanner (Step 0) MUST execute before any other processing",
Check: func(state PipelineState, history []Transition) bool {
if len(history) == 0 {
return true
}
return history[0].From == StateInit && history[0].To == StateScanning
},
},
{
Name: "DecisionLoggerAlwaysFires",
Description: "Every successfully processed event MUST have a decision log entry",
Check: func(state PipelineState, history []Transition) bool {
if state != StateComplete {
return true // Only check on completion.
}
for _, t := range history {
if t.To == StateDecisionLog {
return true
}
}
// Allow completion from dedup (skip path).
for _, t := range history {
if t.Guard == "is_duplicate" {
return true
}
}
return false
},
},
{
Name: "NoSkipToComplete",
Description: "Cannot jump directly from INIT to COMPLETE",
Check: func(state PipelineState, history []Transition) bool {
for _, t := range history {
if t.From == StateInit && t.To == StateComplete {
return false
}
}
return true
},
},
}
// --- Decision Logger Chain Specification ---
// ChainInvariant defines a safety property for the Decision Logger chain.
type ChainInvariant struct {
Name string
Description string
Check func(chain []ChainEntry) bool
}
// ChainEntry is a simplified chain entry for verification.
type ChainEntry struct {
Index int `json:"index"`
Hash string `json:"hash"`
PreviousHash string `json:"previous_hash"`
Signature string `json:"signature"`
}
// ChainInvariants are the safety properties of the Decision Logger.
var ChainInvariants = []ChainInvariant{
{
Name: "GenesisBlockValid",
Description: "First entry MUST have PreviousHash='genesis'",
Check: func(chain []ChainEntry) bool {
if len(chain) == 0 {
return true
}
return chain[0].PreviousHash == "genesis"
},
},
{
Name: "ChainContinuity",
Description: "Each entry[i].PreviousHash MUST equal entry[i-1].Hash",
Check: func(chain []ChainEntry) bool {
for i := 1; i < len(chain); i++ {
if chain[i].PreviousHash != chain[i-1].Hash {
return false
}
}
return true
},
},
{
Name: "NoEmptyHashes",
Description: "No entry may have an empty hash",
Check: func(chain []ChainEntry) bool {
for _, e := range chain {
if e.Hash == "" {
return false
}
}
return true
},
},
{
Name: "MonotonicIndices",
Description: "Chain indices MUST be strictly monotonically increasing",
Check: func(chain []ChainEntry) bool {
for i := 1; i < len(chain); i++ {
if chain[i].Index != chain[i-1].Index+1 {
return false
}
}
return true
},
},
}
// --- Verifier ---
// SpecVerifier runs formal invariant checks.
type SpecVerifier struct {
mu sync.Mutex
logger *slog.Logger
stats VerifierStats
}
// VerifierStats tracks verification results.
type VerifierStats struct {
TotalChecks int64 `json:"total_checks"`
Passed int64 `json:"passed"`
Failed int64 `json:"failed"`
}
// InvariantResult is the outcome of an invariant check.
type InvariantResult struct {
Name string `json:"name"`
Passed bool `json:"passed"`
Details string `json:"details,omitempty"`
}
// NewSpecVerifier creates a new formal spec verifier.
func NewSpecVerifier() *SpecVerifier {
return &SpecVerifier{
logger: slog.Default().With("component", "sec-012-formalspec"),
}
}
// VerifyPipeline checks all Event Bus pipeline invariants.
func (v *SpecVerifier) VerifyPipeline(state PipelineState, history []Transition) []InvariantResult {
var results []InvariantResult
for _, inv := range PipelineInvariants {
v.mu.Lock()
v.stats.TotalChecks++
passed := inv.Check(state, history)
if passed {
v.stats.Passed++
} else {
v.stats.Failed++
}
v.mu.Unlock()
results = append(results, InvariantResult{
Name: inv.Name,
Passed: passed,
Details: fmt.Sprintf("%s: %v", inv.Description, passed),
})
}
return results
}
// VerifyChain checks all Decision Logger chain invariants.
func (v *SpecVerifier) VerifyChain(chain []ChainEntry) []InvariantResult {
var results []InvariantResult
for _, inv := range ChainInvariants {
v.mu.Lock()
v.stats.TotalChecks++
passed := inv.Check(chain)
if passed {
v.stats.Passed++
} else {
v.stats.Failed++
}
v.mu.Unlock()
results = append(results, InvariantResult{
Name: inv.Name,
Passed: passed,
Details: fmt.Sprintf("%s: %v", inv.Description, passed),
})
}
return results
}
// Stats returns verification metrics.
func (v *SpecVerifier) Stats() VerifierStats {
v.mu.Lock()
defer v.mu.Unlock()
return v.stats
}