Repository: cage-challenge/cage-challenge-4

Issue:
Challenge 4 BlueEnterpriseWrapper, incorrect communication policy vectors in observations
[Appendix B - Agent observation](https://github.com/cage-challenge/cage-challenge-4?tab=readme-ov-file#appendix-b--agent-observation) of the README states that agent observation vectors contain the communication policy for each subnet. A value of ```1``` indicates a subnet should be blocked, ```0``` indicates a subnet should not be blocked.

This communication policy vector instead seems to contain a vector where a ```1``` indicates an intended *unblocked* connection with only *adjacent* subnets, and a ```0``` either indicates that the subnet should be blocked *or* that the subnet is simply not adjacent.

For example, in phase 1, Restricted Zone A's communications policy observation is ```[0 0 1 0 1 0 0 0 0]``` where indices 2 and 4 represent the Internet and Operational Zone A subnets respectively.
The [network diagram](https://github.com/cage-challenge/cage-challenge-4/blob/main/documentation/docs/assets/CAGE-Network-Diagram.png) shows that these are the two subnets adjacent to Restricted Zone A, and the [phase 1 communication policy table](https://github.com/cage-challenge/cage-challenge-4?tab=readme-ov-file#table-1-initial-network-communication-security-policy-----mission-pre-planning-phase) states that the two subnets indeed ought to be unblocked by Restricted Zone A, however the communication policy table states that Restricted Zone A should also have unblocked HQ (all of its subnets or just the public access subnet?), Contractor Network, and Restricted Zone B

So I believe Restricted Zone A's communications policy observation in phase 1 should be something like ```[0 0 0 0 0 1 0 0 0]``` where index 5 represents Operational Zone B.

Discussion:

Solution patch:
diff --git a/CybORG/Agents/Wrappers/BlueFlatWrapper.py b/CybORG/Agents/Wrappers/BlueFlatWrapper.py
index 022bcd8..7faa771 100644
--- a/CybORG/Agents/Wrappers/BlueFlatWrapper.py
+++ b/CybORG/Agents/Wrappers/BlueFlatWrapper.py
@@ -120,7 +120,7 @@ class BlueFlatWrapper(BlueFixedActionWrapper):
 
         observations = {
             agent: self.observation_change(agent, obs)
-            for agent, obs in observations.items()
+            for agent, obs in sorted(observations.items())
             if "blue" in agent
         }
         return observations, rewards, terminated, truncated, info
@@ -209,7 +209,8 @@ class BlueFlatWrapper(BlueFixedActionWrapper):
             # Comms
             comms_policy = self.comms_policies[state.mission_phase]
             comms_matrix = nx.to_numpy_array(comms_policy, nodelist=subnet_names)
-            comms_policy_subvector = list(comms_matrix[subnet_names.index(subnet)])
+            comms_policy_subvector = comms_matrix[subnet_names.index(subnet)]
+            comms_policy_subvector = np.logical_not(comms_policy_subvector)
             self.policy[agent_name] = comms_policy
 
             # Process malware events for users, then servers
@@ -267,30 +268,37 @@ class BlueFlatWrapper(BlueFixedActionWrapper):
     def _build_comms_policy_network(self, mission: str):
         hosts = (
             "internet_subnet",
+            "admin_network_subnet",
+            "office_network_subnet",
+            "public_access_zone_subnet",
+            "contractor_network_subnet",
             "restricted_zone_a_subnet",
             "restricted_zone_b_subnet",
-            "contractor_network_subnet",
-            "public_access_zone_subnet",
         )
 
-        links_to_add = (
-            ("restricted_zone_a_subnet", "operational_zone_a_subnet"),
-            ("restricted_zone_b_subnet", "operational_zone_b_subnet"),
-            ("public_access_zone_subnet", "admin_network_subnet"),
-            ("public_access_zone_subnet", "office_network_subnet"),
-        )
-
-        network = nx.star_graph(len(hosts) - 1)
+        network = nx.complete_graph(len(hosts))
         node_mapping = dict(enumerate(hosts))
         network = nx.relabel_nodes(network, node_mapping)
-        network.add_edges_from(links_to_add)
+
+        network.add_edges_from((
+            ("restricted_zone_a_subnet", "operational_zone_a_subnet"),
+            ("restricted_zone_b_subnet", "operational_zone_b_subnet"),
+        ))
 
         if mission == "MissionA":
-            blocked_link = ("restricted_zone_a_subnet", "operational_zone_a_subnet")
-            network.remove_edge(*blocked_link)
+            network.remove_edges_from((
+                ("restricted_zone_a_subnet", "operational_zone_a_subnet"),
+                ("restricted_zone_a_subnet", "contractor_network_subnet"),
+                ("restricted_zone_a_subnet", "restricted_zone_b_subnet"),
+                ("restricted_zone_a_subnet", "internet_subnet"),
+            ))
         elif mission == "MissionB":
-            blocked_link = ("restricted_zone_b_subnet", "operational_zone_b_subnet")
-            network.remove_edge(*blocked_link)
+            network.remove_edges_from((
+                ("restricted_zone_b_subnet", "operational_zone_b_subnet"),
+                ("restricted_zone_b_subnet", "contractor_network_subnet"),
+                ("restricted_zone_b_subnet", "restricted_zone_a_subnet"),
+                ("restricted_zone_b_subnet", "internet_subnet"),
+            ))
 
         return network
 
diff --git a/CybORG/Simulator/Scenarios/EnterpriseScenarioGenerator.py b/CybORG/Simulator/Scenarios/EnterpriseScenarioGenerator.py
index dba256c..be7faf7 100644
--- a/CybORG/Simulator/Scenarios/EnterpriseScenarioGenerator.py
+++ b/CybORG/Simulator/Scenarios/EnterpriseScenarioGenerator.py
@@ -153,7 +153,6 @@ class EnterpriseScenarioGenerator(ScenarioGenerator):
         self._generate_green_agents(hosts, subnets, agents)
         self._generate_red_agents(subnets, agents)
         team_agents = self._generate_team_agents(agents)
-        allowed_subnets_per_mphase = self._set_allowed_subnets_per_mission_phase()
         scenario = Scenario(
             agents=agents,
             team_calcs=None,


Test patch:
diff --git a/CybORG/Tests/test_cc4/test_BlueEnterpriseWrapper.py b/CybORG/Tests/test_cc4/test_BlueEnterpriseWrapper.py
index 4c67958..5b32863 100644
--- a/CybORG/Tests/test_cc4/test_BlueEnterpriseWrapper.py
+++ b/CybORG/Tests/test_cc4/test_BlueEnterpriseWrapper.py
@@ -261,34 +261,57 @@ def mission_phase(request):
 
 @pytest.fixture
 def network(mission_phase):
-    hosts = (
-        'internet_subnet',
+    adjacency_matrices = {
+        "Preplanning": np.array(
+            [[0, 1, 1, 1, 1, 0, 1, 0, 1],
+             [1, 0, 1, 1, 1, 0, 1, 0, 1],
+             [1, 1, 0, 1, 1, 0, 1, 0, 1],
+             [1, 1, 1, 0, 1, 0, 1, 0, 1],
+             [1, 1, 1, 1, 0, 1, 1, 0, 1],
+             [0, 0, 0, 0, 1, 0, 0, 0, 0],
+             [1, 1, 1, 1, 1, 0, 0, 1, 1],
+             [0, 0, 0, 0, 0, 0, 1, 0, 0],
+             [1, 1, 1, 1, 1, 0, 1, 0, 0,]]
+        ), 
+        "MissionA": np.array(
+            [[0, 1, 1, 1, 1, 0, 1, 0, 1],
+             [1, 0, 1, 1, 1, 0, 1, 0, 1],
+             [1, 1, 0, 1, 1, 0, 1, 0, 1],
+             [1, 1, 1, 0, 0, 0, 1, 0, 1],
+             [1, 1, 1, 0, 0, 0, 0, 0, 0],
+             [0, 0, 0, 0, 0, 0, 0, 0, 0],
+             [1, 1, 1, 1, 0, 0, 0, 1, 1],
+             [0, 0, 0, 0, 0, 0, 1, 0, 0],
+             [1, 1, 1, 1, 0, 0, 1, 0, 0,]]
+        ),
+        "MissionB": np.array(
+            [[0, 1, 1, 1, 1, 0, 1, 0, 1],
+             [1, 0, 1, 1, 1, 0, 1, 0, 1],
+             [1, 1, 0, 1, 1, 0, 1, 0, 1],
+             [1, 1, 1, 0, 1, 0, 0, 0, 1],
+             [1, 1, 1, 1, 0, 1, 0, 0, 1],
+             [0, 0, 0, 0, 1, 0, 0, 0, 0],
+             [1, 1, 1, 0, 0, 0, 0, 0, 0],
+             [0, 0, 0, 0, 0, 0, 0, 0, 0],
+             [1, 1, 1, 1, 1, 0, 0, 0, 0,]]
+        ),
+    }
+
+    # The above matrix corresponds to the documentation
+    names = [
+        'office_network_subnet',
+        'admin_network_subnet',
+        'public_access_zone_subnet',
+        'contractor_network_subnet',
         'restricted_zone_a_subnet',
+        'operational_zone_a_subnet',
         'restricted_zone_b_subnet',
-        'contractor_network_subnet',
-        'public_access_zone_subnet'
-    )
-
-    links_to_add = (
-        ('restricted_zone_a_subnet', 'operational_zone_a_subnet'),
-        ('restricted_zone_b_subnet', 'operational_zone_b_subnet'),
-        ('public_access_zone_subnet', 'admin_network_subnet'),
-        ('public_access_zone_subnet', 'office_network_subnet'),
-    )
-
-    network = nx.star_graph(len(hosts)-1)
-    node_mapping = dict(enumerate(hosts))
+        'operational_zone_b_subnet',
+        'internet_subnet', 
+    ]
+    node_mapping = dict(enumerate(names))
+    network = nx.from_numpy_array(adjacency_matrices[mission_phase])
     network = nx.relabel_nodes(network, node_mapping)
-    network.add_edges_from(links_to_add)
-
-    if mission_phase == 'MissionA':
-        blocked_link = ('restricted_zone_a_subnet', 'operational_zone_a_subnet')
-        network.remove_edge(*blocked_link)
-
-    elif mission_phase == 'MissionB':
-        blocked_link = ('restricted_zone_b_subnet', 'operational_zone_b_subnet')
-        network.remove_edge(*blocked_link)
-
     return network
 
 @pytest.fixture
@@ -298,10 +321,8 @@ def expected_comms_policy(network, blue_agent_short, cyborg):
     agent_subnet = state.hostname_subnet_map[agent_hostname]
 
     nodelist = sorted(network.nodes)
-    matrix = nx.to_numpy_array(network,nodelist=nodelist)
-    for i, node in enumerate(nodelist):
-        if node == agent_subnet:
-            return matrix[i]
+    matrix = nx.to_numpy_array(network, nodelist=nodelist)
+    return np.logical_not(matrix[nodelist.index(agent_subnet)])
 
 def test_BlueEnterpriseWrapper_comms_policy_reset(reset_obs_short, blue_agent_short, expected_comms_policy, mission_phase):
     """Tests the ideal comms policy for the mission phase."""
@@ -309,8 +330,6 @@ def test_BlueEnterpriseWrapper_comms_policy_reset(reset_obs_short, blue_agent_sh
         return
 
     comms_block = reset_obs_short[COMMS_POLICY_SLICE]
-    print(comms_block)
-
     assert (comms_block == expected_comms_policy).all()
 
 def test_BlueEnterpriseWrapper_comms_policy_step(cyborg, blue_agent_short, expected_comms_policy, mission_phase):