diff --git a/14_experiments/telemetry_replay/telemetry_replay b/14_experiments/telemetry_replay/telemetry_replay index d871aa14f..74abebda5 100755 Binary files a/14_experiments/telemetry_replay/telemetry_replay and b/14_experiments/telemetry_replay/telemetry_replay differ diff --git a/PHOENIX_PROBLEMS.md b/PHOENIX_PROBLEMS.md index 4ed4dd9ad..72ebfc268 100644 --- a/PHOENIX_PROBLEMS.md +++ b/PHOENIX_PROBLEMS.md @@ -51,3 +51,9 @@ This document tracks critical gaps between PhoenixOS theory and implementation, **Problem:** Proving "Why" an autonomous action was taken. **Expert Assessment:** **Content-Addressable Evidence Ledger**. **Implementation:** Phoenix Ledger (P0) implemented with SHA-256 hash chaining of `(trace_hash, sdi, policy, action, result, time, confidence, replay, experiment)` tuples. + +## 8. Discovered Infrastructure Issues +**Status:** **[NEW]** +- **Issue:** `build_phoenix.sh` lacks execution permissions. +- **Issue:** Test suite fails collection (`ModuleNotFoundError`) due to environment configuration / `PYTHONPATH` issues. +- **Action Required:** Fix script permissions in repository and resolve module resolution path for the test suite. diff --git a/PHOENIX_TASKS.md b/PHOENIX_TASKS.md index b24c10e96..370844fac 100644 --- a/PHOENIX_TASKS.md +++ b/PHOENIX_TASKS.md @@ -28,6 +28,6 @@ ## Validation Metrics - **Build Status:** PASSED (All 9 services). -- **Test Status:** PASSED (Arbiter, Bus, Guard, Ledger, Monitor, Nexus, Sentinel, Trace, Warden). +- **Test Status:** FAILED (Collection Errors: ModuleNotFoundError). - **Latency:** Guard < 50us (Verified). - **Storage:** 3-Tier Trace Eviction (Verified). diff --git a/build_phoenix.sh b/build_phoenix.sh old mode 100644 new mode 100755 diff --git a/config/warden.json b/config/warden.json new file mode 100644 index 000000000..0562b17cc --- /dev/null +++ b/config/warden.json @@ -0,0 +1,8 @@ +{ + "thresholds": { + "safe": 0.3, + "watch": 0.5, + "suspicious": 0.7, + "critical": 0.9 + } +} diff --git a/phoenix_os/agents/internal/game/marl/stability.go b/phoenix_os/agents/internal/game/marl/stability.go new file mode 100644 index 000000000..1d92fcf07 --- /dev/null +++ b/phoenix_os/agents/internal/game/marl/stability.go @@ -0,0 +1,73 @@ +package marl + +import ( + "sync" + "time" +) + +// StabilityController enforces action limits and cooling periods for MARL agents. +type StabilityController struct { + mu sync.RWMutex + actionDebt float64 + lastAction time.Time + cooldown time.Duration + maxContainment float64 + decayRate float64 // Debt reduction per second + lastDecay time.Time +} + +// NewStabilityController initializes a controller with defined limits and decay rate. +func NewStabilityController(cooldown time.Duration, maxContainment float64, decayRate float64) *StabilityController { + now := time.Now() + return &StabilityController{ + cooldown: cooldown, + maxContainment: maxContainment, + decayRate: decayRate, + lastDecay: now, + } +} + +// applyDecay reduces the action debt based on elapsed time. Must be called with lock held. +func (sc *StabilityController) applyDecay(now time.Time) { + elapsed := now.Sub(sc.lastDecay).Seconds() + if elapsed > 0 { + reduction := elapsed * sc.decayRate + sc.actionDebt -= reduction + if sc.actionDebt < 0 { + sc.actionDebt = 0 + } + sc.lastDecay = now + } +} + +// TryRecordAction atomically checks if an action is allowed and records it if so. +// It also applies time-based decay to the action debt. +func (sc *StabilityController) TryRecordAction(cost float64, now time.Time) bool { + sc.mu.Lock() + defer sc.mu.Unlock() + + sc.applyDecay(now) + + // Check Cooldown + if now.Sub(sc.lastAction) < sc.cooldown { + return false + } + + // Check Containment Rate (Action Debt) + if sc.actionDebt+cost > sc.maxContainment { + return false + } + + sc.actionDebt += cost + sc.lastAction = now + return true +} + +// GetActionDebt returns the current action debt (mainly for testing/monitoring). +func (sc *StabilityController) GetActionDebt(now time.Time) float64 { + sc.mu.Lock() + defer sc.mu.Unlock() + sc.applyDecay(now) + return sc.actionDebt +} + diff --git a/phoenix_os/agents/internal/game/marl/stability_test.go b/phoenix_os/agents/internal/game/marl/stability_test.go new file mode 100644 index 000000000..d351014c7 --- /dev/null +++ b/phoenix_os/agents/internal/game/marl/stability_test.go @@ -0,0 +1,60 @@ +package marl + +import ( + "testing" + "time" +) + +func TestStability(t *testing.T) { + // 100ms cooldown, 1.0 max containment, decay 0.0 per sec (for testing strict limits) + sc := NewStabilityController(100*time.Millisecond, 1.0, 0.0) + + start := time.Now() + + // First action should pass + if !sc.TryRecordAction(0.5, start) { + t.Error("Expected action to be allowed") + } + + // Immediate second action should fail due to cooldown + if sc.TryRecordAction(0.1, start.Add(10*time.Millisecond)) { + t.Error("Expected action to be throttled due to cooldown") + } + + // Advance time past cooldown + afterCooldown := start.Add(150 * time.Millisecond) + + // Should pass if under containment limit + if !sc.TryRecordAction(0.4, afterCooldown) { + t.Error("Expected action to be allowed after cooldown") + } + + // Should fail if exceeding limit + if sc.TryRecordAction(0.2, afterCooldown.Add(150*time.Millisecond)) { + t.Error("Expected action to be throttled due to containment limit") + } +} + +func TestStabilityDecay(t *testing.T) { + // Decay 1.0 per second + sc := NewStabilityController(100*time.Millisecond, 1.0, 1.0) + + start := time.Now() + + // Max out containment + sc.TryRecordAction(1.0, start) + + // Should fail + if sc.TryRecordAction(0.1, start.Add(150*time.Millisecond)) { + t.Error("Expected action to be throttled due to containment limit") + } + + // Advance time by 0.5s, debt should decay by 0.5 + afterDecay := start.Add(500 * time.Millisecond) + + // Should pass now that debt has decayed + if !sc.TryRecordAction(0.4, afterDecay) { + t.Error("Expected action to be allowed after debt decay") + } +} + diff --git a/phoenix_os/agents/internal/swarm/governance/governance.go b/phoenix_os/agents/internal/swarm/governance/governance.go new file mode 100644 index 000000000..2c79f062b --- /dev/null +++ b/phoenix_os/agents/internal/swarm/governance/governance.go @@ -0,0 +1,31 @@ +package governance + +import ( + "sync" +) + +// Policy defines the rules for swarm node participation. +type Policy struct { + MinReputation float64 + QuorumSize int +} + +// SwarmGovernor enforces governance policies on the node network. +type SwarmGovernor struct { + mu sync.RWMutex + Policy Policy +} + +// NewSwarmGovernor initializes the governor with a policy. +func NewSwarmGovernor(policy Policy) *SwarmGovernor { + return &SwarmGovernor{ + Policy: policy, + } +} + +// ValidateProposal checks if a node is authorized to participate in consensus. +func (sg *SwarmGovernor) ValidateProposal(nodeReputation float64) bool { + sg.mu.RLock() + defer sg.mu.RUnlock() + return nodeReputation >= sg.Policy.MinReputation +} diff --git a/phoenix_os/agents/internal/swarm/governance/governance_test.go b/phoenix_os/agents/internal/swarm/governance/governance_test.go new file mode 100644 index 000000000..c2c66dfda --- /dev/null +++ b/phoenix_os/agents/internal/swarm/governance/governance_test.go @@ -0,0 +1,16 @@ +package governance + +import "testing" + +func TestGovernance(t *testing.T) { + policy := Policy{MinReputation: 0.7, QuorumSize: 3} + gov := NewSwarmGovernor(policy) + + if gov.ValidateProposal(0.8) == false { + t.Error("Proposal should be accepted for compliant reputation") + } + + if gov.ValidateProposal(0.5) == true { + t.Error("Proposal should be rejected for low reputation") + } +} diff --git a/phoenix_os/bus/artifacts/phoenix_bus b/phoenix_os/bus/artifacts/phoenix_bus index 3343b4ef4..1e8f1917a 100755 Binary files a/phoenix_os/bus/artifacts/phoenix_bus and b/phoenix_os/bus/artifacts/phoenix_bus differ diff --git a/phoenix_os/ledger/artifacts/phoenix_ledger b/phoenix_os/ledger/artifacts/phoenix_ledger old mode 100755 new mode 100644 index c0b4372da..10c6aa660 Binary files a/phoenix_os/ledger/artifacts/phoenix_ledger and b/phoenix_os/ledger/artifacts/phoenix_ledger differ diff --git a/phoenix_os/monitor/artifacts/phoenix_monitor b/phoenix_os/monitor/artifacts/phoenix_monitor index e71731234..a3e596539 100755 Binary files a/phoenix_os/monitor/artifacts/phoenix_monitor and b/phoenix_os/monitor/artifacts/phoenix_monitor differ diff --git a/phoenix_os/telemetry/serialization/go.mod b/phoenix_os/telemetry/serialization/go.mod new file mode 100644 index 000000000..a9f3e6144 --- /dev/null +++ b/phoenix_os/telemetry/serialization/go.mod @@ -0,0 +1,10 @@ +module phoenix/telemetry/serialization + +go 1.25.0 + +require github.com/segmentio/encoding v0.5.4 + +require ( + github.com/segmentio/asm v1.1.3 // indirect + golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect +) diff --git a/phoenix_os/telemetry/serialization/go.sum b/phoenix_os/telemetry/serialization/go.sum new file mode 100644 index 000000000..0c88e6dc9 --- /dev/null +++ b/phoenix_os/telemetry/serialization/go.sum @@ -0,0 +1,6 @@ +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 h1:WecRHqgE09JBkh/584XIE6PMz5KKE/vER4izNUi30AQ= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/phoenix_os/telemetry/serialization/optimizer.go b/phoenix_os/telemetry/serialization/optimizer.go new file mode 100644 index 000000000..06697e2ed --- /dev/null +++ b/phoenix_os/telemetry/serialization/optimizer.go @@ -0,0 +1,11 @@ +package serialization + +import ( + "encoding/json" + "github.com/segmentio/encoding/json" // Utilizing high-performance JSON encoder +) + +// OptimizedMarshaler replaces standard library JSON encoding with high-perf alternatives. +func OptimizedMarshaler(v interface{}) ([]byte, error) { + return json.Marshal(v) +} diff --git a/phoenix_os/telemetry/serialization/optimizer_test.go b/phoenix_os/telemetry/serialization/optimizer_test.go new file mode 100644 index 000000000..c7b5b6b24 --- /dev/null +++ b/phoenix_os/telemetry/serialization/optimizer_test.go @@ -0,0 +1,21 @@ +package serialization + +import ( + "testing" +) + +type TestStruct struct { + ID int `json:"id"` + Value string `json:"value"` +} + +func TestOptimizer(t *testing.T) { + data := TestStruct{ID: 1, Value: "test"} + encoded, err := OptimizedMarshaler(data) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + if string(encoded) == "" { + t.Error("Marshaled data is empty") + } +} diff --git a/phoenix_os/warden/src/warden.go b/phoenix_os/warden/src/warden.go index 135d88c26..b71ce23d5 100644 --- a/phoenix_os/warden/src/warden.go +++ b/phoenix_os/warden/src/warden.go @@ -1,7 +1,9 @@ package main import ( + "encoding/json" "fmt" + "os" ) type SystemState uint8 @@ -14,16 +16,37 @@ const ( StateCompromised SystemState = 4 ) +type Config struct { + Thresholds struct { + Safe float64 `json:"safe"` + Watch float64 `json:"watch"` + Suspicious float64 `json:"suspicious"` + Critical float64 `json:"critical"` + } `json:"thresholds"` +} + type Warden struct { CurrentState SystemState Throttling float64 // 0.0 (None) to 1.0 (Full Block) + Config Config } -func NewWarden() *Warden { +func NewWarden(configPath string) (*Warden, error) { + configFile, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) + } + + var config Config + if err := json.Unmarshal(configFile, &config); err != nil { + return nil, fmt.Errorf("failed to parse config file: %w", err) + } + return &Warden{ CurrentState: StateSafe, Throttling: 0.0, - } + Config: config, + }, nil } // EvaluateSDI maps the System Disorder Index to a discrete state @@ -31,13 +54,13 @@ func (w *Warden) EvaluateSDI(sdi float64) { oldState := w.CurrentState switch { - case sdi < 0.3: + case sdi < w.Config.Thresholds.Safe: w.CurrentState = StateSafe - case sdi < 0.5: + case sdi < w.Config.Thresholds.Watch: w.CurrentState = StateWatch - case sdi < 0.7: + case sdi < w.Config.Thresholds.Suspicious: w.CurrentState = StateSuspicious - case sdi < 0.9: + case sdi < w.Config.Thresholds.Critical: w.CurrentState = StateCritical default: w.CurrentState = StateCompromised @@ -71,7 +94,11 @@ func (w *Warden) ApplyAction() { func main() { fmt.Println("Phoenix Warden starting with Finite-State Controller...") - warden := NewWarden() + warden, err := NewWarden("../../config/warden.json") + if err != nil { + fmt.Printf("Error initializing Warden: %v\n", err) + os.Exit(1) + } // Simulate rising threat warden.EvaluateSDI(0.15) // Safe diff --git a/phoenix_os/warden/src/warden_test.go b/phoenix_os/warden/src/warden_test.go index fb9070a94..56cd92844 100644 --- a/phoenix_os/warden/src/warden_test.go +++ b/phoenix_os/warden/src/warden_test.go @@ -1,10 +1,32 @@ package main -import "testing" +import ( + "os" + "testing" +) func TestWardenFSM(t *testing.T) { - w := NewWarden() - + // Create a temporary config file for the test + configContent := `{ + "thresholds": { + "safe": 0.3, + "watch": 0.5, + "suspicious": 0.7, + "critical": 0.9 + } + }` + configFile := "test_warden.json" + err := os.WriteFile(configFile, []byte(configContent), 0644) + if err != nil { + t.Fatalf("Failed to create temp config file: %v", err) + } + defer os.Remove(configFile) + + w, err := NewWarden(configFile) + if err != nil { + t.Fatalf("Failed to create Warden with config: %v", err) + } + // Initial State if w.CurrentState != StateSafe { t.Errorf("Expected initial state Safe, got %v", w.CurrentState) diff --git a/tests/__pycache__/test_orchestrator.cpython-313-pytest-9.0.3.pyc b/tests/__pycache__/test_orchestrator.cpython-313-pytest-9.0.3.pyc index ff289cb91..0bcc88ef8 100644 Binary files a/tests/__pycache__/test_orchestrator.cpython-313-pytest-9.0.3.pyc and b/tests/__pycache__/test_orchestrator.cpython-313-pytest-9.0.3.pyc differ diff --git a/tests/__pycache__/test_service.cpython-313-pytest-9.0.3.pyc b/tests/__pycache__/test_service.cpython-313-pytest-9.0.3.pyc index bcc2243b7..b74ae9ad0 100644 Binary files a/tests/__pycache__/test_service.cpython-313-pytest-9.0.3.pyc and b/tests/__pycache__/test_service.cpython-313-pytest-9.0.3.pyc differ