mirror of
https://github.com/syntrex-lab/gomcp.git
synced 2026-04-25 04:16:22 +02:00
232 lines
6.5 KiB
Go
232 lines
6.5 KiB
Go
// Package entropy implements the Entropy Gate — a DIP H0.3 component
|
|
// that measures Shannon entropy of text signals and blocks anomalous patterns.
|
|
//
|
|
// Core thesis: destructive intent exhibits higher entropy (noise/chaos),
|
|
// while constructive intent exhibits lower entropy (structured/coherent).
|
|
// The gate measures entropy at each processing step and triggers apoptosis
|
|
// (pipeline kill) when entropy exceeds safe thresholds.
|
|
package entropy
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// GateConfig configures the entropy gate thresholds.
|
|
type GateConfig struct {
|
|
// MaxEntropy is the maximum allowed Shannon entropy (bits/char).
|
|
// Typical English text: 3.5-4.5 bits/char.
|
|
// Random/adversarial text: 5.5+ bits/char.
|
|
// Default: 5.0 (generous for multilingual content).
|
|
MaxEntropy float64
|
|
|
|
// MaxEntropyGrowth is the maximum allowed entropy increase
|
|
// between iterations. If entropy grows faster than this,
|
|
// the signal is likely diverging (destructive recursion).
|
|
// Default: 0.5 bits/char per iteration.
|
|
MaxEntropyGrowth float64
|
|
|
|
// MinTextLength is the minimum text length to analyze.
|
|
// Shorter texts have unreliable entropy. Default: 20.
|
|
MinTextLength int
|
|
|
|
// MaxIterationsWithoutDecline triggers apoptosis if entropy
|
|
// hasn't decreased in N consecutive iterations. Default: 3.
|
|
MaxIterationsWithoutDecline int
|
|
}
|
|
|
|
// DefaultGateConfig returns sensible defaults.
|
|
func DefaultGateConfig() GateConfig {
|
|
return GateConfig{
|
|
MaxEntropy: 5.0,
|
|
MaxEntropyGrowth: 0.5,
|
|
MinTextLength: 20,
|
|
MaxIterationsWithoutDecline: 3,
|
|
}
|
|
}
|
|
|
|
// GateResult holds the result of an entropy gate check.
|
|
type GateResult struct {
|
|
Entropy float64 `json:"entropy"` // Shannon entropy in bits/char
|
|
CharCount int `json:"char_count"` // Number of characters analyzed
|
|
UniqueChars int `json:"unique_chars"` // Number of unique characters
|
|
IsAllowed bool `json:"is_allowed"` // Signal passed the gate
|
|
IsBlocked bool `json:"is_blocked"` // Signal was blocked (apoptosis)
|
|
BlockReason string `json:"block_reason,omitempty"` // Why it was blocked
|
|
EntropyDelta float64 `json:"entropy_delta"` // Change from previous measurement
|
|
}
|
|
|
|
// Gate performs entropy-based signal analysis.
|
|
type Gate struct {
|
|
cfg GateConfig
|
|
history []float64 // entropy history across iterations
|
|
iterationsFlat int // consecutive iterations without entropy decline
|
|
}
|
|
|
|
// NewGate creates a new Entropy Gate.
|
|
func NewGate(cfg *GateConfig) *Gate {
|
|
c := DefaultGateConfig()
|
|
if cfg != nil {
|
|
if cfg.MaxEntropy > 0 {
|
|
c.MaxEntropy = cfg.MaxEntropy
|
|
}
|
|
if cfg.MaxEntropyGrowth > 0 {
|
|
c.MaxEntropyGrowth = cfg.MaxEntropyGrowth
|
|
}
|
|
if cfg.MinTextLength > 0 {
|
|
c.MinTextLength = cfg.MinTextLength
|
|
}
|
|
if cfg.MaxIterationsWithoutDecline > 0 {
|
|
c.MaxIterationsWithoutDecline = cfg.MaxIterationsWithoutDecline
|
|
}
|
|
}
|
|
return &Gate{cfg: c}
|
|
}
|
|
|
|
// Check evaluates a text signal and returns whether it should pass.
|
|
// Call this on each iteration of a recursive loop.
|
|
func (g *Gate) Check(text string) *GateResult {
|
|
charCount := utf8.RuneCountInString(text)
|
|
|
|
result := &GateResult{
|
|
CharCount: charCount,
|
|
IsAllowed: true,
|
|
}
|
|
|
|
// Too short for reliable analysis — allow by default.
|
|
if charCount < g.cfg.MinTextLength {
|
|
result.Entropy = 0
|
|
return result
|
|
}
|
|
|
|
// Compute Shannon entropy.
|
|
e := ShannonEntropy(text)
|
|
result.Entropy = e
|
|
result.UniqueChars = countUniqueRunes(text)
|
|
|
|
// Check 1: Absolute entropy threshold.
|
|
if e > g.cfg.MaxEntropy {
|
|
result.IsAllowed = false
|
|
result.IsBlocked = true
|
|
result.BlockReason = fmt.Sprintf(
|
|
"entropy %.3f exceeds max %.3f (signal too chaotic)",
|
|
e, g.cfg.MaxEntropy)
|
|
}
|
|
|
|
// Check 2: Entropy growth rate.
|
|
if len(g.history) > 0 {
|
|
prev := g.history[len(g.history)-1]
|
|
result.EntropyDelta = e - prev
|
|
|
|
if result.EntropyDelta > g.cfg.MaxEntropyGrowth {
|
|
result.IsAllowed = false
|
|
result.IsBlocked = true
|
|
result.BlockReason = fmt.Sprintf(
|
|
"entropy growth %.3f exceeds max %.3f (divergent recursion)",
|
|
result.EntropyDelta, g.cfg.MaxEntropyGrowth)
|
|
}
|
|
|
|
// Track iterations without decline.
|
|
// Use epsilon tolerance because ShannonEntropy iterates a map,
|
|
// and Go randomizes map iteration order, causing micro-different
|
|
// float64 results for the same text due to addition ordering.
|
|
const epsilon = 1e-9
|
|
if e >= prev-epsilon {
|
|
g.iterationsFlat++
|
|
} else {
|
|
g.iterationsFlat = 0
|
|
}
|
|
|
|
// Check 3: Stagnation (recursive collapse).
|
|
if g.iterationsFlat >= g.cfg.MaxIterationsWithoutDecline {
|
|
result.IsAllowed = false
|
|
result.IsBlocked = true
|
|
result.BlockReason = fmt.Sprintf(
|
|
"entropy stagnant for %d iterations (recursive collapse)",
|
|
g.iterationsFlat)
|
|
}
|
|
}
|
|
|
|
g.history = append(g.history, e)
|
|
return result
|
|
}
|
|
|
|
// Reset clears the gate state for a new pipeline.
|
|
func (g *Gate) Reset() {
|
|
g.history = nil
|
|
g.iterationsFlat = 0
|
|
}
|
|
|
|
// History returns the entropy measurements across iterations.
|
|
func (g *Gate) History() []float64 {
|
|
h := make([]float64, len(g.history))
|
|
copy(h, g.history)
|
|
return h
|
|
}
|
|
|
|
// ShannonEntropy computes Shannon entropy in bits per character.
|
|
// H(X) = -Σ p(x) * log2(p(x))
|
|
func ShannonEntropy(text string) float64 {
|
|
if len(text) == 0 {
|
|
return 0
|
|
}
|
|
|
|
// Count character frequencies.
|
|
freq := make(map[rune]int)
|
|
total := 0
|
|
for _, r := range text {
|
|
freq[r]++
|
|
total++
|
|
}
|
|
|
|
// Compute entropy.
|
|
var h float64
|
|
for _, count := range freq {
|
|
p := float64(count) / float64(total)
|
|
if p > 0 {
|
|
h -= p * math.Log2(p)
|
|
}
|
|
}
|
|
return h
|
|
}
|
|
|
|
// AnalyzeText provides a comprehensive entropy analysis of text.
|
|
func AnalyzeText(text string) map[string]interface{} {
|
|
charCount := utf8.RuneCountInString(text)
|
|
wordCount := len(strings.Fields(text))
|
|
uniqueChars := countUniqueRunes(text)
|
|
entropy := ShannonEntropy(text)
|
|
|
|
// Theoretical maximum entropy for this character set.
|
|
maxEntropy := 0.0
|
|
if uniqueChars > 0 {
|
|
maxEntropy = math.Log2(float64(uniqueChars))
|
|
}
|
|
|
|
// Redundancy: how much structure the text has.
|
|
// 0 = maximum entropy (random), 1 = minimum entropy (all same char).
|
|
redundancy := 0.0
|
|
if maxEntropy > 0 {
|
|
redundancy = 1.0 - (entropy / maxEntropy)
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"entropy": entropy,
|
|
"max_entropy": maxEntropy,
|
|
"redundancy": redundancy,
|
|
"char_count": charCount,
|
|
"word_count": wordCount,
|
|
"unique_chars": uniqueChars,
|
|
"bits_per_word": 0.0,
|
|
}
|
|
}
|
|
|
|
func countUniqueRunes(s string) int {
|
|
seen := make(map[rune]struct{})
|
|
for _, r := range s {
|
|
seen[r] = struct{}{}
|
|
}
|
|
return len(seen)
|
|
}
|