Skip to content

[P2] feat(swarm): Implement Byzantine Swarm Protection#620

Closed
fallofpheonix wants to merge 5 commits into
mainfrom
feature/issue-128-byzantine-protection-v2
Closed

[P2] feat(swarm): Implement Byzantine Swarm Protection#620
fallofpheonix wants to merge 5 commits into
mainfrom
feature/issue-128-byzantine-protection-v2

Conversation

@fallofpheonix
Copy link
Copy Markdown
Owner

@fallofpheonix fallofpheonix commented May 21, 2026

Resolves #128. Implemented SwarmGovernor to enforce node reputation policies and quorum consensus requirements.

Summary by Sourcery

Introduce governance, stability, and telemetry components to enforce configurable safety policies, while externalizing Warden thresholds to JSON configuration and covering new behavior with unit tests.

New Features:

  • Add SwarmGovernor to enforce swarm node reputation policies for consensus participation
  • Introduce MARL StabilityController to regulate agent containment actions via cooldowns and containment limits
  • Provide an optimized telemetry JSON marshaler using a high-performance encoding library
  • Load Warden state thresholds from an external JSON configuration file

Tests:

  • Add unit tests for Warden configuration-driven FSM behavior
  • Add tests for MARL StabilityController cooldown and containment enforcement
  • Add tests for SwarmGovernor reputation-based proposal validation
  • Add tests for the optimized telemetry JSON marshaler

Copilot AI review requested due to automatic review settings May 21, 2026 13:26
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 21, 2026

Reviewer's Guide

Introduces configurable warden thresholds, a MARL agent stability controller, swarm governance with reputation-based proposal validation, and an optimized JSON serialization helper, along with tests and configuration wiring.

File-Level Changes

Change Details Files
Make Warden state thresholds configurable via external JSON config and wire into main and tests.
  • Add Config struct with threshold fields and store it on Warden
  • Change NewWarden to load and parse thresholds from a JSON config file and return errors
  • Update EvaluateSDI to use configured thresholds instead of hard-coded constants
  • Update main to initialize Warden from ../../config/warden.json with error handling
  • Adjust warden tests to create a temporary config file and construct Warden via the new initializer
phoenix_os/warden/src/warden.go
phoenix_os/warden/src/warden_test.go
config/warden.json
Introduce StabilityController to rate-limit MARL agent containment actions based on cooldown and containment budget.
  • Add StabilityController with mutex-protected ActionDebt, LastAction, Cooldown, and MaxContainment fields
  • Implement NewStabilityController constructor to initialize limits
  • Implement CanAct to enforce cooldown and max containment checks before allowing an action
  • Implement RecordAction to update ActionDebt and LastAction timestamps
  • Add unit test covering cooldown behavior and containment limit enforcement
phoenix_os/agents/internal/game/marl/stability.go
phoenix_os/agents/internal/game/marl/stability_test.go
Add SwarmGovernor to enforce node reputation policy for swarm governance and validate consensus participation.
  • Define Policy struct with MinReputation and QuorumSize fields
  • Implement SwarmGovernor with RWMutex and stored Policy
  • Add NewSwarmGovernor constructor to initialize with a policy
  • Implement ValidateProposal to authorize nodes based on MinReputation threshold
  • Add unit test verifying proposals are accepted or rejected based on reputation
phoenix_os/agents/internal/swarm/governance/governance.go
phoenix_os/agents/internal/swarm/governance/governance_test.go
Introduce optimized telemetry JSON serialization using segmentio encoder and basic marshaling test.
  • Create OptimizedMarshaler that marshals using github.com/segmentio/encoding/json
  • Add go.mod and go.sum for telemetry/serialization module with segmentio dependencies
  • Add unit test ensuring OptimizedMarshaler successfully marshals a sample struct
phoenix_os/telemetry/serialization/optimizer.go
phoenix_os/telemetry/serialization/optimizer_test.go
phoenix_os/telemetry/serialization/go.mod
phoenix_os/telemetry/serialization/go.sum

Assessment against linked issues

Issue Objective Addressed Explanation
#128 Implement Proof-of-Authority consensus with weighted quorum based on node reputation in the swarm (consensus changes). The PR introduces a SwarmGovernor with a Policy containing MinReputation and QuorumSize, but there is no implementation of Proof-of-Authority logic or weighted quorum evaluation. QuorumSize is defined but never used, and no consensus/validator selection or voting logic is present in the diff.
#128 Introduce a node reputation scoring mechanism that integrates with governance/consensus (reputation changes). Node reputation is only represented as a float parameter passed into ValidateProposal, with a simple MinReputation threshold check. There is no code to compute, track, update, or persist node reputation scores, nor integration of such a system into the broader swarm or consensus logic.
#128 Add governance tests that validate PoA + reputation + weighted quorum behavior (governance tests). The added governance_test.go only tests that ValidateProposal accepts or rejects based on a static MinReputation threshold. It does not exercise any PoA behavior, quorum size handling, or weighted quorum consensus. The tests do not cover quorum-related behavior at all, and therefore do not meet the scope described in the issue.

Possibly linked issues

  • #: PR introduces SwarmGovernor enforcing reputation thresholds and quorum rules, directly addressing Byzantine Swarm Protection requirements.
  • #N/A: PR’s SwarmGovernor introduces reputation-based node validation, directly contributing to PoA’s reputation-weighted quorum consensus.
  • #[INTEGRATION] Arbiter: Reputation-Weighted Quorum Implementation: PR’s SwarmGovernor uses node reputation and quorum policies, directly implementing the reputation-weighted quorum integration described.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Warning

Rate limit exceeded

@fallofpheonix has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 55 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69d89a88-fbbc-4517-8e8c-6eb8b8be506d

📥 Commits

Reviewing files that changed from the base of the PR and between 2cac688 and 867ae51.

⛔ Files ignored due to path filters (3)
  • phoenix_os/telemetry/serialization/go.sum is excluded by !**/*.sum
  • tests/__pycache__/test_orchestrator.cpython-313-pytest-9.0.3.pyc is excluded by !**/*.pyc
  • tests/__pycache__/test_service.cpython-313-pytest-9.0.3.pyc is excluded by !**/*.pyc
📒 Files selected for processing (17)
  • 14_experiments/telemetry_replay/telemetry_replay
  • PHOENIX_PROBLEMS.md
  • PHOENIX_TASKS.md
  • build_phoenix.sh
  • config/warden.json
  • phoenix_os/agents/internal/game/marl/stability.go
  • phoenix_os/agents/internal/game/marl/stability_test.go
  • phoenix_os/agents/internal/swarm/governance/governance.go
  • phoenix_os/agents/internal/swarm/governance/governance_test.go
  • phoenix_os/bus/artifacts/phoenix_bus
  • phoenix_os/ledger/artifacts/phoenix_ledger
  • phoenix_os/monitor/artifacts/phoenix_monitor
  • phoenix_os/telemetry/serialization/go.mod
  • phoenix_os/telemetry/serialization/optimizer.go
  • phoenix_os/telemetry/serialization/optimizer_test.go
  • phoenix_os/warden/src/warden.go
  • phoenix_os/warden/src/warden_test.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/issue-128-byzantine-protection-v2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • In optimizer.go you're importing both the standard encoding/json and github.com/segmentio/encoding/json under the same name and then calling json.Marshal, which will not compile as-is; either remove the stdlib import or alias one of the packages (e.g. segjson) and call it explicitly.
  • The StabilityController separates CanAct and RecordAction under different lock scopes, so concurrent callers can all pass CanAct and then update ActionDebt later, allowing the total to exceed MaxContainment; consider combining the check and update in a single method or keeping the lock held between the two steps.
  • The NewWarden constructor now requires a concrete path and main uses a hard-coded relative path (../../config/warden.json), which is brittle across environments; consider accepting an io.Reader or injecting thresholds/config externally so callers can provide configuration without depending on repo-relative paths.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `optimizer.go` you're importing both the standard `encoding/json` and `github.com/segmentio/encoding/json` under the same name and then calling `json.Marshal`, which will not compile as-is; either remove the stdlib import or alias one of the packages (e.g. `segjson`) and call it explicitly.
- The `StabilityController` separates `CanAct` and `RecordAction` under different lock scopes, so concurrent callers can all pass `CanAct` and then update `ActionDebt` later, allowing the total to exceed `MaxContainment`; consider combining the check and update in a single method or keeping the lock held between the two steps.
- The `NewWarden` constructor now requires a concrete path and `main` uses a hard-coded relative path (`../../config/warden.json`), which is brittle across environments; consider accepting an `io.Reader` or injecting thresholds/config externally so callers can provide configuration without depending on repo-relative paths.

## Individual Comments

### Comment 1
<location path="phoenix_os/warden/src/warden.go" line_range="34-50" />
<code_context>
 }

-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
 }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Validate the loaded thresholds to ensure they are sane and ordered.

Given that `EvaluateSDI` relies on strictly increasing thresholds (Safe < Watch < Suspicious < Critical) and normalized values, a misconfigured JSON (e.g., non-monotonic, equal, zero, or >1 thresholds) could cause incorrect or collapsed state transitions. Please add post-unmarshal validation (e.g., monotonicity check and ensuring all thresholds are in [0,1]) and return an error if the config is invalid.

```suggestion
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)
	}

	if err := validateConfig(&config); err != nil {
		return nil, fmt.Errorf("invalid config: %w", err)
	}

	return &Warden{
		CurrentState: StateSafe,
		Throttling:   0.0,
		Config:       config,
	}, nil
}

func validateConfig(config *Config) error {
	t := config.Thresholds

	// Ensure each threshold is normalized and non-zero: (0, 1]
	if t.Safe <= 0 || t.Safe > 1 {
		return fmt.Errorf("threshold 'safe' must be in (0, 1], got %f", t.Safe)
	}
	if t.Watch <= 0 || t.Watch > 1 {
		return fmt.Errorf("threshold 'watch' must be in (0, 1], got %f", t.Watch)
	}
	if t.Suspicious <= 0 || t.Suspicious > 1 {
		return fmt.Errorf("threshold 'suspicious' must be in (0, 1], got %f", t.Suspicious)
	}
	if t.Critical <= 0 || t.Critical > 1 {
		return fmt.Errorf("threshold 'critical' must be in (0, 1], got %f", t.Critical)
	}

	// Ensure strictly increasing thresholds: Safe < Watch < Suspicious < Critical
	if !(t.Safe < t.Watch && t.Watch < t.Suspicious && t.Suspicious < t.Critical) {
		return fmt.Errorf(
			"thresholds must be strictly increasing (safe < watch < suspicious < critical), got safe=%f, watch=%f, suspicious=%f, critical=%f",
			t.Safe, t.Watch, t.Suspicious, t.Critical,
		)
	}

	return nil
}
```
</issue_to_address>

### Comment 2
<location path="phoenix_os/warden/src/warden.go" line_range="97" />
<code_context>
 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)
</code_context>
<issue_to_address>
**suggestion:** Consider making the config path configurable rather than hardcoded relative paths.

Hardcoding "../../config/warden.json" couples startup to a specific working directory and layout, which is likely to break under different launch setups (systemd, containers, tests). Prefer a configurable source (e.g., flag, env var, or embedded default with override) so startup doesn’t depend on cwd.
</issue_to_address>

### Comment 3
<location path="phoenix_os/warden/src/warden_test.go" line_range="18-23" />
<code_context>
+			"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)
</code_context>
<issue_to_address>
**suggestion (testing):** Use t.TempDir and unique paths instead of a fixed filename in the working directory

A fixed filename in the working directory can cause tests to race with each other, break under concurrent runs, or fail on read-only directories. Use t.TempDir() and build the path with filepath.Join so each test gets its own temp file and automatic cleanup, instead of relying on os.Remove.

Suggested implementation:

```golang
	// Create a config file in a temporary directory for the test
	configContent := `{
		"thresholds": {
			"safe": 0.3,
			"watch": 0.5,
			"suspicious": 0.7,
			"critical": 0.9
		}
	}`

	dir := t.TempDir()
	configFile := filepath.Join(dir, "warden_config.json")

	err := os.WriteFile(configFile, []byte(configContent), 0o644)
	if err != nil {
		t.Fatalf("Failed to create temp config file: %v", err)
	}

	w, err := NewWarden(configFile)

```

1. Ensure `filepath` is imported at the top of `warden_test.go`:
   - Add `path/filepath` to the import block, e.g. `import "path/filepath"`, or to the existing grouped imports if present.
2. If other tests in this file also create temporary config files with fixed names in the working directory, consider updating them similarly to use `t.TempDir()` and `filepath.Join` for consistency and to avoid race conditions.
</issue_to_address>

### Comment 4
<location path="phoenix_os/telemetry/serialization/optimizer_test.go" line_range="12-18" />
<code_context>
+	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")
+	}
</code_context>
<issue_to_address>
**suggestion (testing):** Strengthen assertions by validating the actual JSON output and error paths

Asserting only that the output is non-empty is too weak. Please:

- Assert the exact JSON output for this struct (or decode into a map if field order is unstable) to verify tags and field mapping.
- Add a negative test using an unsupported type and assert that `OptimizedMarshaler` returns a non-nil error.

This will better verify correctness and error handling of the marshaler.

Suggested implementation:

```golang
import (
	"encoding/json"
	"testing"
)

```

```golang
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 len(encoded) == 0 {
		t.Fatal("Marshaled data is empty")
	}

	var decoded map[string]interface{}
	if err := json.Unmarshal(encoded, &decoded); err != nil {
		t.Fatalf("Failed to unmarshal encoded JSON: %v", err)
	}

	id, ok := decoded["id"].(float64)
	if !ok || id != 1 {
		t.Fatalf("Unexpected id field: %#v", decoded["id"])
	}

	value, ok := decoded["value"].(string)
	if !ok || value != "test" {
		t.Fatalf("Unexpected value field: %#v", decoded["value"])
	}
}

func TestOptimizerUnsupportedType(t *testing.T) {
	type Unsupported struct {
		Ch chan int `json:"ch"`
	}

	data := Unsupported{Ch: make(chan int)}
	encoded, err := OptimizedMarshaler(data)
	if err == nil {
		t.Fatalf("Expected error for unsupported type, got nil (encoded=%q)", string(encoded))
	}
}

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +34 to 50
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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Validate the loaded thresholds to ensure they are sane and ordered.

Given that EvaluateSDI relies on strictly increasing thresholds (Safe < Watch < Suspicious < Critical) and normalized values, a misconfigured JSON (e.g., non-monotonic, equal, zero, or >1 thresholds) could cause incorrect or collapsed state transitions. Please add post-unmarshal validation (e.g., monotonicity check and ensuring all thresholds are in [0,1]) and return an error if the config is invalid.

Suggested change
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
}
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)
}
if err := validateConfig(&config); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
return &Warden{
CurrentState: StateSafe,
Throttling: 0.0,
Config: config,
}, nil
}
func validateConfig(config *Config) error {
t := config.Thresholds
// Ensure each threshold is normalized and non-zero: (0, 1]
if t.Safe <= 0 || t.Safe > 1 {
return fmt.Errorf("threshold 'safe' must be in (0, 1], got %f", t.Safe)
}
if t.Watch <= 0 || t.Watch > 1 {
return fmt.Errorf("threshold 'watch' must be in (0, 1], got %f", t.Watch)
}
if t.Suspicious <= 0 || t.Suspicious > 1 {
return fmt.Errorf("threshold 'suspicious' must be in (0, 1], got %f", t.Suspicious)
}
if t.Critical <= 0 || t.Critical > 1 {
return fmt.Errorf("threshold 'critical' must be in (0, 1], got %f", t.Critical)
}
// Ensure strictly increasing thresholds: Safe < Watch < Suspicious < Critical
if !(t.Safe < t.Watch && t.Watch < t.Suspicious && t.Suspicious < t.Critical) {
return fmt.Errorf(
"thresholds must be strictly increasing (safe < watch < suspicious < critical), got safe=%f, watch=%f, suspicious=%f, critical=%f",
t.Safe, t.Watch, t.Suspicious, t.Critical,
)
}
return nil
}

func main() {
fmt.Println("Phoenix Warden starting with Finite-State Controller...")
warden := NewWarden()
warden, err := NewWarden("../../config/warden.json")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider making the config path configurable rather than hardcoded relative paths.

Hardcoding "../../config/warden.json" couples startup to a specific working directory and layout, which is likely to break under different launch setups (systemd, containers, tests). Prefer a configurable source (e.g., flag, env var, or embedded default with override) so startup doesn’t depend on cwd.

Comment on lines +18 to +23
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Use t.TempDir and unique paths instead of a fixed filename in the working directory

A fixed filename in the working directory can cause tests to race with each other, break under concurrent runs, or fail on read-only directories. Use t.TempDir() and build the path with filepath.Join so each test gets its own temp file and automatic cleanup, instead of relying on os.Remove.

Suggested implementation:

	// Create a config file in a temporary directory for the test
	configContent := `{
		"thresholds": {
			"safe": 0.3,
			"watch": 0.5,
			"suspicious": 0.7,
			"critical": 0.9
		}
	}`

	dir := t.TempDir()
	configFile := filepath.Join(dir, "warden_config.json")

	err := os.WriteFile(configFile, []byte(configContent), 0o644)
	if err != nil {
		t.Fatalf("Failed to create temp config file: %v", err)
	}

	w, err := NewWarden(configFile)
  1. Ensure filepath is imported at the top of warden_test.go:
    • Add path/filepath to the import block, e.g. import "path/filepath", or to the existing grouped imports if present.
  2. If other tests in this file also create temporary config files with fixed names in the working directory, consider updating them similarly to use t.TempDir() and filepath.Join for consistency and to avoid race conditions.

Comment on lines +12 to +18
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) == "" {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Strengthen assertions by validating the actual JSON output and error paths

Asserting only that the output is non-empty is too weak. Please:

  • Assert the exact JSON output for this struct (or decode into a map if field order is unstable) to verify tags and field mapping.
  • Add a negative test using an unsupported type and assert that OptimizedMarshaler returns a non-nil error.

This will better verify correctness and error handling of the marshaler.

Suggested implementation:

import (
	"encoding/json"
	"testing"
)
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 len(encoded) == 0 {
		t.Fatal("Marshaled data is empty")
	}

	var decoded map[string]interface{}
	if err := json.Unmarshal(encoded, &decoded); err != nil {
		t.Fatalf("Failed to unmarshal encoded JSON: %v", err)
	}

	id, ok := decoded["id"].(float64)
	if !ok || id != 1 {
		t.Fatalf("Unexpected id field: %#v", decoded["id"])
	}

	value, ok := decoded["value"].(string)
	if !ok || value != "test" {
		t.Fatalf("Unexpected value field: %#v", decoded["value"])
	}
}

func TestOptimizerUnsupportedType(t *testing.T) {
	type Unsupported struct {
		Ch chan int `json:"ch"`
	}

	data := Unsupported{Ch: make(chan int)}
	encoded, err := OptimizedMarshaler(data)
	if err == nil {
		t.Fatalf("Expected error for unsupported type, got nil (encoded=%q)", string(encoded))
	}
}

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 291faba52d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +27 to +30
func (sg *SwarmGovernor) ValidateProposal(nodeReputation float64) bool {
sg.mu.RLock()
defer sg.mu.RUnlock()
return nodeReputation >= sg.Policy.MinReputation
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce quorum in proposal validation

ValidateProposal only checks MinReputation and never uses Policy.QuorumSize, so proposals can be accepted even when the consensus set is below the configured quorum. In this commit, QuorumSize is defined and set in tests but never read, which means the advertised Byzantine/quorum protection is not actually enforced.

Useful? React with 👍 / 👎.

Comment on lines +36 to +40
if sc.ActionDebt+cost > sc.MaxContainment {
return false
}

return true
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make action check and debt update atomic

CanAct and RecordAction are separate locked calls, so concurrent callers can both pass CanAct before either records debt, then both call RecordAction and exceed MaxContainment. This creates a TOCTOU race that bypasses the containment cap under parallel agent actions.

Useful? React with 👍 / 👎.

func main() {
fmt.Println("Phoenix Warden starting with Finite-State Controller...")
warden := NewWarden()
warden, err := NewWarden("../../config/warden.json")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a resolvable default config path

The hard-coded "../../config/warden.json" is resolved from the process working directory, not from the source file location, so normal launches (for example from repo root or as a service) will often fail to find the config and exit at startup. The committed config lives at config/warden.json, so this default path is brittle and frequently incorrect.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@fallofpheonix
Copy link
Copy Markdown
Owner Author

Closing: Duplicate of #614 (v2 branch). The Byzantine Swarm Protection module and the speculative agents/internal/swarm/ directory have been deleted during Phase 1 consolidation. Distributed consensus (L7: Swarm Coordination) is deferred to Phase 3+ per Axiom #6.

@fallofpheonix fallofpheonix deleted the feature/issue-128-byzantine-protection-v2 branch May 21, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P2] [Phoenix Arbiter] Implement Byzantine Swarm Protection

2 participants