hirann commited on
Commit
8143530
·
verified ·
1 Parent(s): 87b418f

Upload training/dataset_generator.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. training/dataset_generator.py +840 -840
training/dataset_generator.py CHANGED
@@ -1,840 +1,840 @@
1
- """
2
- ImmunoOrg 2.0: Dataset Generation for GRPO Training
3
- =====================================================
4
-
5
- Generates 1,200+ training scenarios optimized for GRPO training:
6
- 1. Curriculum Learning (300 scenarios, Difficulty 1→4)
7
- 2. Edge Case Coverage (400 scenarios, 12 failure modes)
8
- 3. Balanced Complexity Matrix (300 scenarios, all difficulty×attack×org combos)
9
- 4. Co-Evolution Progression (200 scenarios, adversary adaptation feedback)
10
- """
11
-
12
- import json
13
- import gzip
14
- import random
15
- from typing import List, Dict, Any, Optional
16
- from dataclasses import dataclass, asdict
17
- from pathlib import Path
18
- import logging
19
-
20
- # Configure logging
21
- logging.basicConfig(
22
- level=logging.INFO,
23
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
24
- )
25
- logger = logging.getLogger(__name__)
26
-
27
-
28
- # ============================================================
29
- # DATA CLASSES & CONFIGS
30
- # ============================================================
31
-
32
- @dataclass
33
- class DatasetConfig:
34
- """Configuration for dataset generation."""
35
- dataset_type: str # "curriculum" | "edge_case" | "complexity_matrix" | "coevolution"
36
- output_dir: str = "training/datasets"
37
- include_metadata: bool = True
38
- compress_output: bool = True
39
- verbose: bool = True
40
-
41
-
42
- @dataclass
43
- class ScenarioConfig:
44
- """Environment configuration for a single scenario."""
45
- difficulty: int
46
- network_size: int
47
- departments: int
48
- silos: int
49
- max_steps: int
50
- attack_count: int
51
- expected_reward_min: float
52
- expected_reward_max: float
53
- attack_vectors: List[str] = None
54
- directives: List[str] = None
55
- edge_case_type: Optional[str] = None
56
- stage: Optional[str] = None
57
- generation: Optional[int] = None
58
-
59
-
60
- # ============================================================
61
- # DATASET GENERATOR
62
- # ============================================================
63
-
64
- class DatasetGenerator:
65
- """
66
- Generates training datasets for GRPO training.
67
-
68
- This class handles:
69
- - Curriculum learning scenarios (progressive difficulty)
70
- - Edge case scenarios (specific failure modes)
71
- - Balanced complexity matrices (systematic coverage)
72
- - Co-evolution scenarios (adversary adaptation)
73
- """
74
-
75
- # Curriculum configurations by difficulty level
76
- CURRICULUM_CONFIGS = {
77
- 1: {
78
- "network_size": 7,
79
- "departments": 3,
80
- "silos": 0,
81
- "max_steps": 50,
82
- "attack_count": 1,
83
- "expected_reward_min": 0.10,
84
- "expected_reward_max": 0.20,
85
- "attack_vectors": ["SQL_INJECTION", "XSS", "CREDENTIAL_STUFFING"],
86
- "description": "Single-point attacks, simple blocking"
87
- },
88
- 2: {
89
- "network_size": 12,
90
- "departments": 4,
91
- "silos": 0,
92
- "max_steps": 100,
93
- "attack_count": 2,
94
- "expected_reward_min": 0.14,
95
- "expected_reward_max": 0.30,
96
- "attack_vectors": ["LATERAL_MOVEMENT", "PRIVILEGE_ESCALATION", "PHISHING"],
97
- "description": "Multi-node with lateral spread, timeline reconstruction"
98
- },
99
- 3: {
100
- "network_size": 18,
101
- "departments": 6,
102
- "silos": 2,
103
- "max_steps": 150,
104
- "attack_count": 3,
105
- "expected_reward_min": 0.18,
106
- "expected_reward_max": 0.35,
107
- "attack_vectors": ["RANSOMWARE", "SUPPLY_CHAIN", "DDOS"],
108
- "description": "Cascading failures + silos, org refactor needed"
109
- },
110
- 4: {
111
- "network_size": 23,
112
- "departments": 8,
113
- "silos": 3,
114
- "max_steps": 200,
115
- "attack_count": 5,
116
- "expected_reward_min": 0.22,
117
- "expected_reward_max": 0.42,
118
- "attack_vectors": ["APT_BACKDOOR", "ZERO_DAY", "LATERAL_MOVEMENT"],
119
- "description": "APT with persistent backdoors, total restructuring"
120
- }
121
- }
122
-
123
- # Edge case categories with scenario counts
124
- EDGE_CASES = {
125
- "war_room_deadlock": {"count": 40, "difficulties": [2, 3, 4]},
126
- "silo_bottleneck": {"count": 40, "difficulties": [2, 3, 4]},
127
- "false_positive": {"count": 35, "difficulties": [1, 2, 3]},
128
- "stealth_attack": {"count": 35, "difficulties": [2, 3, 4]},
129
- "cascading_failure": {"count": 35, "difficulties": [3, 4]},
130
- "belief_divergence": {"count": 30, "difficulties": [2, 3, 4]},
131
- "approval_confusion": {"count": 30, "difficulties": [1, 2, 3]},
132
- "directive_conflict": {"count": 30, "difficulties": [2, 3, 4]},
133
- "ransomware_spread": {"count": 30, "difficulties": [3, 4]},
134
- "supply_chain": {"count": 30, "difficulties": [3, 4]},
135
- "pipeline_breach": {"count": 25, "difficulties": [3, 4]},
136
- "org_chart_ambiguity": {"count": 25, "difficulties": [2, 3, 4]},
137
- }
138
-
139
- # Complexity matrix dimensions
140
- COMPLEXITY_DIMENSIONS = {
141
- "difficulties": [1, 2, 3, 4],
142
- "attack_vectors": [
143
- "SQL_INJECTION", "XSS", "CREDENTIAL_STUFFING",
144
- "LATERAL_MOVEMENT", "PRIVILEGE_ESCALATION",
145
- "RANSOMWARE", "APT_BACKDOOR", "ZERO_DAY"
146
- ],
147
- "org_configs": [
148
- {"depts": 3, "silos": 0},
149
- {"depts": 4, "silos": 0},
150
- {"depts": 6, "silos": 1},
151
- {"depts": 6, "silos": 2},
152
- {"depts": 8, "silos": 2},
153
- {"depts": 8, "silos": 3}
154
- ],
155
- "directives": [None, "uptime_first", "security_first", "compliance_first", "conflicting"]
156
- }
157
-
158
- # Co-evolution adversary complexity by generation
159
- COEVOLUTION_GENERATIONS = {
160
- 0: {"stealth": 0.3, "vectors": 1, "adaptation": 0.0, "knowledge": 0.0},
161
- 1: {"stealth": 0.5, "vectors": 2, "adaptation": 0.2, "knowledge": 0.1},
162
- 2: {"stealth": 0.7, "vectors": 3, "adaptation": 0.4, "knowledge": 0.2},
163
- 3: {"stealth": 0.8, "vectors": 4, "adaptation": 0.6, "knowledge": 0.3},
164
- 4: {"stealth": 0.9, "vectors": 5, "adaptation": 0.8, "knowledge": 0.4}
165
- }
166
-
167
- def __init__(self, config: DatasetConfig):
168
- """Initialize dataset generator with configuration."""
169
- self.config = config
170
- self.output_dir = Path(config.output_dir)
171
- self.output_dir.mkdir(parents=True, exist_ok=True)
172
- self.scenario_counter = 0
173
-
174
- if config.verbose:
175
- logger.info(f"Initialized DatasetGenerator")
176
- logger.info(f"Output directory: {self.output_dir.absolute()}")
177
-
178
- # ========================================================
179
- # CURRICULUM LEARNING DATASET (300 scenarios)
180
- # ========================================================
181
-
182
- def generate_curriculum_dataset(self) -> List[Dict[str, Any]]:
183
- """
184
- Generate curriculum learning dataset: Difficulty 1 → 2 → 3 → 4.
185
-
186
- Returns:
187
- List of scenario dictionaries
188
- """
189
- logger.info("Generating Curriculum Learning Dataset (300 scenarios)...")
190
- scenarios = []
191
-
192
- for difficulty in [1, 2, 3, 4]:
193
- config = self.CURRICULUM_CONFIGS[difficulty]
194
- scenarios_for_difficulty = []
195
- count = 75 # 75 scenarios per difficulty
196
-
197
- for i in range(count):
198
- self.scenario_counter += 1
199
- scenario = {
200
- "scenario_id": f"CL_L{difficulty}_{self.scenario_counter:03d}",
201
- "dataset_type": "curriculum",
202
- "difficulty": difficulty,
203
- "stage": f"Level{difficulty}",
204
- "stage_description": config["description"],
205
- "seed": 100 + difficulty * 1000 + i,
206
- "config": {
207
- "network_size": config["network_size"],
208
- "departments": config["departments"],
209
- "silos": config["silos"],
210
- "max_steps": config["max_steps"],
211
- "attack_count": config["attack_count"],
212
- "attack_vectors": config["attack_vectors"],
213
- "expected_reward_range": [config["expected_reward_min"], config["expected_reward_max"]]
214
- },
215
- "metadata": {
216
- "curriculum_stage": difficulty,
217
- "requires_previous_success": difficulty > 1,
218
- "recommended_training_epochs": 5 - difficulty + 1
219
- }
220
- }
221
- scenarios_for_difficulty.append(scenario)
222
-
223
- scenarios.extend(scenarios_for_difficulty)
224
- logger.info(f" Difficulty {difficulty}: {count} scenarios")
225
-
226
- logger.info(f"✓ Curriculum dataset complete: {len(scenarios)} scenarios")
227
- return scenarios
228
-
229
- # ========================================================
230
- # EDGE CASE DATASET (400 scenarios)
231
- # ========================================================
232
-
233
- def generate_edge_case_dataset(self) -> List[Dict[str, Any]]:
234
- """
235
- Generate edge case scenarios covering 12 failure modes.
236
-
237
- Returns:
238
- List of scenario dictionaries
239
- """
240
- logger.info("Generating Edge Case Dataset (400 scenarios)...")
241
- scenarios = []
242
- total_scenarios = sum(cfg["count"] for cfg in self.EDGE_CASES.values())
243
-
244
- for edge_case_type, edge_cfg in self.EDGE_CASES.items():
245
- count = edge_cfg["count"]
246
- difficulties = edge_cfg["difficulties"]
247
-
248
- for i in range(count):
249
- self.scenario_counter += 1
250
- # Distribute scenarios across difficulties
251
- difficulty = difficulties[i % len(difficulties)]
252
-
253
- scenario = {
254
- "scenario_id": f"EC_{edge_case_type.upper()}_{self.scenario_counter:03d}",
255
- "dataset_type": "edge_case",
256
- "edge_case_type": edge_case_type,
257
- "difficulty": difficulty,
258
- "seed": 2000 + self.scenario_counter,
259
- "config": self._get_edge_case_config(edge_case_type, difficulty, i),
260
- "metadata": {
261
- "failure_mode": edge_case_type,
262
- "expected_agent_challenge": "high",
263
- "tests_robustness": True
264
- }
265
- }
266
- scenarios.append(scenario)
267
-
268
- logger.info(f" {edge_case_type}: {count} scenarios")
269
-
270
- logger.info(f"✓ Edge case dataset complete: {len(scenarios)} scenarios")
271
- return scenarios
272
-
273
- def _get_edge_case_config(self, edge_case_type: str, difficulty: int, index: int) -> Dict[str, Any]:
274
- """Get edge case-specific configuration."""
275
- base_config = self.CURRICULUM_CONFIGS.get(difficulty, self.CURRICULUM_CONFIGS[1])
276
-
277
- edge_case_specifics = {
278
- "war_room_deadlock": {
279
- "war_room_scenario": True,
280
- "deadlock_turns": 6 + (difficulty - 1) * 2,
281
- "personas_count": 3 + difficulty
282
- },
283
- "silo_bottleneck": {
284
- "silos": max(1, difficulty - 1),
285
- "approval_delays": [4 + difficulty, 3 + difficulty],
286
- "requires_org_refactor": True
287
- },
288
- "false_positive": {
289
- "decoy_attacks": 2 + difficulty,
290
- "real_attack_clarity": 1.0 - (0.1 * difficulty)
291
- },
292
- "stealth_attack": {
293
- "attack_stealth": 0.8 + (0.05 * difficulty),
294
- "evasion_techniques": ["no_log_entries", "low_bandwidth", "mimic_legitimate_traffic"]
295
- },
296
- "cascading_failure": {
297
- "cascading_enabled": True,
298
- "failure_chain_length": 2 + difficulty,
299
- "propagation_speed": 0.5 + (0.1 * difficulty)
300
- },
301
- "belief_divergence": {
302
- "ground_truth_divergence": 0.4 + (0.1 * difficulty),
303
- "agent_model_accuracy": 0.5
304
- },
305
- "approval_confusion": {
306
- "authority_ambiguity": True,
307
- "overlapping_depts": 2 + difficulty
308
- },
309
- "directive_conflict": {
310
- "conflicting_directives": True,
311
- "directive_count": 2 + difficulty
312
- },
313
- "ransomware_spread": {
314
- "ransomware_nodes": 2 + difficulty,
315
- "encryption_speed": 0.6 + (0.1 * difficulty)
316
- },
317
- "supply_chain": {
318
- "external_attack": True,
319
- "dependency_vulnerability": True
320
- },
321
- "pipeline_breach": {
322
- "pipeline_gates": ["ast", "semantic", "terraform", "microvm"],
323
- "gates_bypassed": min(3, difficulty)
324
- },
325
- "org_chart_ambiguity": {
326
- "ambiguous_authority": True,
327
- "overlapping_depts": 2
328
- }
329
- }
330
-
331
- return {
332
- **base_config,
333
- **edge_case_specifics.get(edge_case_type, {})
334
- }
335
-
336
- # ========================================================
337
- # COMPLEXITY MATRIX DATASET (300 scenarios)
338
- # ========================================================
339
-
340
- def generate_complexity_matrix_dataset(self) -> List[Dict[str, Any]]:
341
- """
342
- Generate balanced complexity matrix: uniform coverage of all
343
- difficulty × attack × org_config × directive combinations.
344
-
345
- Returns:
346
- List of scenario dictionaries
347
- """
348
- logger.info("Generating Complexity Matrix Dataset (300 scenarios)...")
349
- scenarios = []
350
-
351
- # Calculate total combinations
352
- total_combos = (
353
- len(self.COMPLEXITY_DIMENSIONS["difficulties"]) *
354
- len(self.COMPLEXITY_DIMENSIONS["attack_vectors"]) *
355
- len(self.COMPLEXITY_DIMENSIONS["org_configs"]) *
356
- len(self.COMPLEXITY_DIMENSIONS["directives"])
357
- )
358
- logger.info(f" Total possible combinations: {total_combos}")
359
- logger.info(f" Sampling: 300 (Latin Hypercube stratification)")
360
-
361
- # Latin Hypercube sampling for even coverage
362
- samples_needed = 300
363
- used_combos = set()
364
-
365
- for i in range(samples_needed):
366
- self.scenario_counter += 1
367
-
368
- # Stratified random sampling
369
- difficulty = random.choice(self.COMPLEXITY_DIMENSIONS["difficulties"])
370
- attack = random.choice(self.COMPLEXITY_DIMENSIONS["attack_vectors"])
371
- org_config = random.choice(self.COMPLEXITY_DIMENSIONS["org_configs"])
372
- directive = random.choice(self.COMPLEXITY_DIMENSIONS["directives"])
373
-
374
- combo_key = (difficulty, attack, org_config["depts"], org_config["silos"], directive)
375
-
376
- scenario = {
377
- "scenario_id": f"CM_{self.scenario_counter:03d}",
378
- "dataset_type": "complexity_matrix",
379
- "difficulty": difficulty,
380
- "seed": 3000 + self.scenario_counter,
381
- "matrix_position": {
382
- "difficulty": difficulty,
383
- "primary_attack": attack,
384
- "org_depts": org_config["depts"],
385
- "org_silos": org_config["silos"],
386
- "directive_type": directive
387
- },
388
- "config": self._get_matrix_config(difficulty, attack, org_config, directive),
389
- "metadata": {
390
- "coverage_category": "balanced_sampling",
391
- "ensures_generalization": True
392
- }
393
- }
394
- scenarios.append(scenario)
395
- used_combos.add(combo_key)
396
-
397
- logger.info(f" Unique combinations covered: {len(used_combos)}/{total_combos}")
398
- logger.info(f"✓ Complexity matrix dataset complete: {len(scenarios)} scenarios")
399
- return scenarios
400
-
401
- def _get_matrix_config(self, difficulty: int, attack: str, org_config: Dict, directive: Optional[str]) -> Dict[str, Any]:
402
- """Get complexity matrix configuration."""
403
- base_config = self.CURRICULUM_CONFIGS.get(difficulty, self.CURRICULUM_CONFIGS[1])
404
-
405
- return {
406
- "difficulty": difficulty,
407
- "network_size": base_config["network_size"],
408
- "departments": org_config["depts"],
409
- "silos": org_config["silos"],
410
- "max_steps": base_config["max_steps"],
411
- "attack_vectors": [attack],
412
- "attack_count": base_config["attack_count"],
413
- "directive": directive,
414
- "expected_reward_range": [base_config["expected_reward_min"], base_config["expected_reward_max"]]
415
- }
416
-
417
- # ========================================================
418
- # CO-EVOLUTION DATASET (200 scenarios)
419
- # ========================================================
420
-
421
- def generate_coevolution_dataset(self) -> List[Dict[str, Any]]:
422
- """
423
- Generate co-evolution progression: adversary adapts over generations.
424
-
425
- Returns:
426
- List of scenario dictionaries
427
- """
428
- logger.info("Generating Co-Evolution Dataset (200 scenarios)...")
429
- scenarios = []
430
-
431
- generations = [0, 1, 2, 3, 4]
432
- scenarios_per_gen = {0: 50, 1: 40, 2: 40, 3: 40, 4: 30}
433
-
434
- for gen in generations:
435
- count = scenarios_per_gen[gen]
436
- adversary_complexity = self.COEVOLUTION_GENERATIONS[gen]
437
-
438
- for i in range(count):
439
- self.scenario_counter += 1
440
-
441
- scenario = {
442
- "scenario_id": f"COEV_G{gen}_{self.scenario_counter:03d}",
443
- "dataset_type": "coevolution",
444
- "generation": gen,
445
- "difficulty": 2 + (gen // 2), # Difficulty increases with generation
446
- "seed": 4000 + gen * 1000 + i,
447
- "adversary_complexity": adversary_complexity,
448
- "config": self._get_coevolution_config(gen),
449
- "metadata": {
450
- "adversary_stealth": adversary_complexity["stealth"],
451
- "num_attack_vectors": adversary_complexity["vectors"],
452
- "adaptation_speed": adversary_complexity["adaptation"],
453
- "expected_difficulty": "increasing",
454
- "tests_meta_learning": True
455
- }
456
- }
457
- scenarios.append(scenario)
458
-
459
- logger.info(f" Generation {gen}: {count} scenarios (stealth={adversary_complexity['stealth']:.1f})")
460
-
461
- logger.info(f"✓ Co-evolution dataset complete: {len(scenarios)} scenarios")
462
- return scenarios
463
-
464
- def _get_coevolution_config(self, generation: int) -> Dict[str, Any]:
465
- """Get co-evolution configuration for a generation."""
466
- difficulty = min(4, 2 + (generation // 2))
467
- base_config = self.CURRICULUM_CONFIGS[difficulty]
468
-
469
- return {
470
- "generation": generation,
471
- "difficulty": difficulty,
472
- "network_size": base_config["network_size"],
473
- "departments": base_config["departments"],
474
- "silos": base_config["silos"],
475
- "max_steps": base_config["max_steps"],
476
- "attack_count": base_config["attack_count"],
477
- "attack_vectors": base_config["attack_vectors"],
478
- "expected_reward_range": [
479
- base_config["expected_reward_min"] + (generation * 0.05),
480
- base_config["expected_reward_max"] + (generation * 0.08)
481
- ]
482
- }
483
-
484
- # ========================================================
485
- # ELITE JUDGE SCENARIO MIX (balanced conflict coverage)
486
- # ========================================================
487
-
488
- def generate_elite_scenario_mix_dataset(self, total: int = 500) -> List[Dict[str, Any]]:
489
- """
490
- Balanced mix of the 5 judge-facing training scenarios (20% each by default).
491
-
492
- These scenarios are designed to be *conflict-heavy* (not random resets):
493
- - RAG grounding (precision mitigation vs blunt isolation)
494
- - Executive alignment / HITL (uptime directive forbids downtime-heavy tactics)
495
- - Silo-breaker (org friction / repeated tactical denial)
496
- - Stealth & persistence (multi-step investigation)
497
- - Adaptive defense / co-evolution pressure (adversary adaptation ramps)
498
-
499
- Notes:
500
- - The `hooks` field is consumed by `training/trajectory_generator.py` via
501
- `training/scenario_hooks.py` to shape rollouts beyond plain `reset()`.
502
- """
503
- if total % 5 != 0:
504
- raise ValueError("total must be divisible by 5 for an even 20/20/20/20/20 split")
505
-
506
- per = total // 5
507
- logger.info(f"Generating Elite Scenario Mix Dataset ({total} scenarios = {per} each type)...")
508
-
509
- families = [
510
- ("basic_containment", "Phase1_BasicCompetence"),
511
- ("rag_grounding", "Phase2_Intelligence"),
512
- ("executive_alignment", "Phase3_Alignment"),
513
- ("silo_breaker", "Phase4_Strategy"),
514
- ("stealth_adaptive", "Phase5_Robustness"),
515
- ]
516
-
517
- scenarios: List[Dict[str, Any]] = []
518
-
519
- def base_cfg(difficulty: int) -> Dict[str, Any]:
520
- cfg = self.CURRICULUM_CONFIGS[difficulty]
521
- return {
522
- "network_size": cfg["network_size"],
523
- "departments": cfg["departments"],
524
- "silos": cfg["silos"],
525
- "max_steps": cfg["max_steps"],
526
- "attack_count": cfg["attack_count"],
527
- "attack_vectors": cfg["attack_vectors"],
528
- "expected_reward_range": [cfg["expected_reward_min"], cfg["expected_reward_max"]],
529
- }
530
-
531
- for family, phase in families:
532
- for i in range(per):
533
- self.scenario_counter += 1
534
-
535
- if family == "basic_containment":
536
- difficulty = 1 if i % 2 == 0 else 2
537
- hooks: Dict[str, Any] = {}
538
- elif family == "rag_grounding":
539
- difficulty = 2 if i % 2 == 0 else 3
540
- hooks = {
541
- "inject_rag_best_mitigation": True,
542
- "attack_vector": "APT_BACKDOOR",
543
- "best_mitigation_chain": ["snapshot_forensics", "deploy_patch"],
544
- }
545
- elif family == "executive_alignment":
546
- difficulty = 2 if i % 2 == 0 else 3
547
- hooks = {"board_uptime_no_isolate": True}
548
- elif family == "silo_breaker":
549
- difficulty = 3 if i % 2 == 0 else 4
550
- hooks = {"force_denials_on_isolate": True}
551
- elif family == "stealth_adaptive":
552
- difficulty = 3 if i % 2 == 0 else 4
553
- hooks = {
554
- "stealthy_initial_attack": True,
555
- "stealth": 0.90 + (0.02 * (i % 3)),
556
- "severity": 0.40 + (0.03 * (i % 4)),
557
- "suppress_initial_logs": True,
558
- }
559
- # Within the final 20% bucket, alternate pure-stealth vs co-evolution pressure.
560
- if i % 2 == 1:
561
- hooks["boost_adversary_adaptation"] = True
562
- hooks["adaptation_counter"] = 8 + (i % 10)
563
- else:
564
- # Should never happen, but keeps mypy/pyright happy in editors.
565
- difficulty = 2
566
- hooks = {}
567
-
568
- scenario = {
569
- "scenario_id": f"ELITE_{family.upper()}_{self.scenario_counter:04d}",
570
- "dataset_type": "elite_scenario_mix",
571
- "family": family,
572
- "curriculum_phase": phase,
573
- "difficulty": difficulty,
574
- "seed": 9000 + self.scenario_counter,
575
- "task": "curriculum_levels_1_to_4",
576
- "config": base_cfg(difficulty),
577
- "hooks": hooks,
578
- "metadata": {
579
- "judge_scenario_family": family,
580
- "training_curriculum_phase": phase,
581
- "mix_policy": "20/20/20/20/20",
582
- },
583
- }
584
- scenarios.append(scenario)
585
-
586
- logger.info(f"✓ Elite scenario mix complete: {len(scenarios)} scenarios")
587
- return scenarios
588
-
589
- # ========================================================
590
- # ELITE JUDGE SCENARIO MIX (balanced conflict coverage)
591
- # ========================================================
592
-
593
- def generate_elite_scenario_mix_dataset(self, total: int = 500) -> List[Dict[str, Any]]:
594
- """Balanced 20/20/20/20/20 mix of the 5 judge-facing training scenarios.
595
-
596
- Families (consumed by ``training/scenario_hooks.py``):
597
- - basic_containment : phase-appropriate baseline tactics
598
- - rag_grounding : precise CVE mitigation vs blunt isolation
599
- - executive_alignment : board uptime directive forbids isolation
600
- - silo_breaker : approver always denies isolate; pivot to org
601
- - stealth_adaptive : high-stealth attack + adversary adaptation pressure
602
- """
603
- if total % 5 != 0:
604
- raise ValueError("total must be divisible by 5 for an even 20/20/20/20/20 split")
605
-
606
- per = total // 5
607
- logger.info(
608
- f"Generating Elite Scenario Mix Dataset ({total} scenarios = {per} each family)..."
609
- )
610
-
611
- families = [
612
- ("basic_containment", "Phase1_BasicCompetence"),
613
- ("rag_grounding", "Phase2_Intelligence"),
614
- ("executive_alignment", "Phase3_Alignment"),
615
- ("silo_breaker", "Phase4_Strategy"),
616
- ("stealth_adaptive", "Phase5_Robustness"),
617
- ]
618
-
619
- def base_cfg(difficulty: int) -> Dict[str, Any]:
620
- cfg = self.CURRICULUM_CONFIGS[difficulty]
621
- return {
622
- "network_size": cfg["network_size"],
623
- "departments": cfg["departments"],
624
- "silos": cfg["silos"],
625
- "max_steps": cfg["max_steps"],
626
- "attack_count": cfg["attack_count"],
627
- "attack_vectors": cfg["attack_vectors"],
628
- "expected_reward_range": [
629
- cfg["expected_reward_min"],
630
- cfg["expected_reward_max"],
631
- ],
632
- }
633
-
634
- scenarios: List[Dict[str, Any]] = []
635
- for family, phase in families:
636
- for i in range(per):
637
- self.scenario_counter += 1
638
-
639
- if family == "basic_containment":
640
- difficulty = 1 if i % 2 == 0 else 2
641
- hooks: Dict[str, Any] = {}
642
- elif family == "rag_grounding":
643
- difficulty = 2 if i % 2 == 0 else 3
644
- hooks = {
645
- "inject_rag_best_mitigation": True,
646
- "attack_vector": "apt_backdoor",
647
- "best_mitigation_chain": ["snapshot_forensics", "deploy_patch"],
648
- }
649
- elif family == "executive_alignment":
650
- difficulty = 2 if i % 2 == 0 else 3
651
- hooks = {"board_uptime_no_isolate": True}
652
- elif family == "silo_breaker":
653
- difficulty = 3 if i % 2 == 0 else 4
654
- hooks = {"force_denials_on_isolate": True}
655
- elif family == "stealth_adaptive":
656
- difficulty = 3 if i % 2 == 0 else 4
657
- hooks = {
658
- "stealthy_initial_attack": True,
659
- "stealth": 0.90 + (0.02 * (i % 3)),
660
- "severity": 0.40 + (0.03 * (i % 4)),
661
- "suppress_initial_logs": True,
662
- }
663
- if i % 2 == 1:
664
- hooks["boost_adversary_adaptation"] = True
665
- hooks["adaptation_counter"] = 8 + (i % 10)
666
- else:
667
- difficulty, hooks = 2, {}
668
-
669
- scenarios.append({
670
- "scenario_id": f"ELITE_{family.upper()}_{self.scenario_counter:04d}",
671
- "dataset_type": "elite_scenario_mix",
672
- "family": family,
673
- "curriculum_phase": phase,
674
- "difficulty": difficulty,
675
- "seed": 9000 + self.scenario_counter,
676
- "task": "curriculum_levels_1_to_4",
677
- "config": base_cfg(difficulty),
678
- "hooks": hooks,
679
- "metadata": {
680
- "judge_scenario_family": family,
681
- "training_curriculum_phase": phase,
682
- "mix_policy": "20/20/20/20/20",
683
- },
684
- })
685
-
686
- logger.info(f"Elite scenario mix complete: {len(scenarios)} scenarios")
687
- return scenarios
688
-
689
- # ========================================================
690
- # SAVE METHODS
691
- # ========================================================
692
-
693
- def save_dataset(self, scenarios: List[Dict[str, Any]], filename: str) -> str:
694
- """
695
- Save dataset to file (optionally compressed).
696
-
697
- Args:
698
- scenarios: List of scenario dictionaries
699
- filename: Output filename
700
-
701
- Returns:
702
- Path to saved file
703
- """
704
- output_path = self.output_dir / filename
705
-
706
- if self.config.compress_output and filename.endswith('.json'):
707
- output_path = output_path.with_suffix('.json.gz')
708
- with gzip.open(str(output_path), 'wt', encoding='utf-8') as f:
709
- json.dump(scenarios, f, indent=2)
710
- logger.info(f"Saved {len(scenarios)} scenarios to {output_path} (compressed)")
711
- else:
712
- with open(output_path, 'w', encoding='utf-8') as f:
713
- json.dump(scenarios, f, indent=2)
714
- logger.info(f"Saved {len(scenarios)} scenarios to {output_path}")
715
-
716
- return str(output_path)
717
-
718
- # ========================================================
719
- # MAIN GENERATION METHOD
720
- # ========================================================
721
-
722
- def generate_all_datasets(self) -> Dict[str, str]:
723
- """
724
- Generate all core dataset types plus the elite judge scenario mix.
725
-
726
- Returns:
727
- Dictionary mapping dataset names to file paths
728
- """
729
- logger.info("=" * 70)
730
- logger.info("STARTING COMPLETE DATASET GENERATION")
731
- logger.info("=" * 70)
732
-
733
- results = {}
734
-
735
- # 1. Curriculum Learning
736
- logger.info("\n[1/4] CURRICULUM LEARNING DATASET")
737
- curriculum_scenarios = self.generate_curriculum_dataset()
738
- curriculum_path = self.save_dataset(curriculum_scenarios, "curriculum_dataset.json")
739
- results["curriculum"] = curriculum_path
740
-
741
- # 2. Edge Cases
742
- logger.info("\n[2/4] EDGE CASE DATASET")
743
- edge_case_scenarios = self.generate_edge_case_dataset()
744
- edge_case_path = self.save_dataset(edge_case_scenarios, "edge_case_dataset.json")
745
- results["edge_case"] = edge_case_path
746
-
747
- # 3. Complexity Matrix
748
- logger.info("\n[3/4] COMPLEXITY MATRIX DATASET")
749
- matrix_scenarios = self.generate_complexity_matrix_dataset()
750
- matrix_path = self.save_dataset(matrix_scenarios, "complexity_matrix_dataset.json")
751
- results["complexity_matrix"] = matrix_path
752
-
753
- # 4. Co-Evolution
754
- logger.info("\n[4/5] CO-EVOLUTION DATASET")
755
- coevolution_scenarios = self.generate_coevolution_dataset()
756
- coevolution_path = self.save_dataset(coevolution_scenarios, "coevolution_dataset.json")
757
- results["coevolution"] = coevolution_path
758
-
759
- # 5. Elite judge scenario mix (balanced conflict coverage)
760
- logger.info("\n[5/5] ELITE SCENARIO MIX (JUDGE-TIER)")
761
- elite_scenarios = self.generate_elite_scenario_mix_dataset(total=500)
762
- elite_path = self.save_dataset(elite_scenarios, "elite_scenario_mix_dataset.json")
763
- results["elite_scenario_mix"] = elite_path
764
-
765
- # Summary
766
- logger.info("\n" + "=" * 70)
767
- logger.info("DATASET GENERATION COMPLETE")
768
- logger.info("=" * 70)
769
- self._print_summary(
770
- curriculum_scenarios,
771
- edge_case_scenarios,
772
- matrix_scenarios,
773
- coevolution_scenarios,
774
- elite_scenarios,
775
- )
776
-
777
- return results
778
-
779
- def _print_summary(self, curriculum, edge_cases, matrix, coevolution, elite_mix):
780
- """Print a formatted summary of generated datasets."""
781
- total_scenarios = len(curriculum) + len(edge_cases) + len(matrix) + len(coevolution) + len(elite_mix)
782
-
783
- summary = f"""
784
- DATASET GENERATION SUMMARY
785
- ==========================
786
-
787
- Curriculum Learning Dataset:
788
- - Total scenarios: {len(curriculum)}
789
- - Difficulty 1: {len([s for s in curriculum if s['difficulty'] == 1])} scenarios
790
- - Difficulty 2: {len([s for s in curriculum if s['difficulty'] == 2])} scenarios
791
- - Difficulty 3: {len([s for s in curriculum if s['difficulty'] == 3])} scenarios
792
- - Difficulty 4: {len([s for s in curriculum if s['difficulty'] == 4])} scenarios
793
-
794
- Edge Case Dataset:
795
- - Total scenarios: {len(edge_cases)}
796
- - Coverage: {len(self.EDGE_CASES)} failure modes
797
-
798
- Complexity Matrix Dataset:
799
- - Total scenarios: {len(matrix)}
800
- - Coverage: {len(set((s['matrix_position']['difficulty'], s['matrix_position']['primary_attack']) for s in matrix))} difficulty×attack combos
801
-
802
- Co-Evolution Dataset:
803
- - Total scenarios: {len(coevolution)}
804
- - Generations: {len(set(s['generation'] for s in coevolution))}
805
-
806
- Elite Scenario Mix (Judge-tier):
807
- - Total scenarios: {len(elite_mix)}
808
- - Families: {len(set(s.get('family') for s in elite_mix))}
809
-
810
- GRAND TOTAL: {total_scenarios} scenarios across all datasets
811
- """
812
- logger.info(summary)
813
-
814
-
815
- # ============================================================
816
- # CLI UTILITY
817
- # ============================================================
818
-
819
- def main():
820
- """Generate all datasets (CLI entry point)."""
821
- config = DatasetConfig(
822
- dataset_type="all",
823
- output_dir="training/datasets",
824
- include_metadata=True,
825
- compress_output=True,
826
- verbose=True
827
- )
828
-
829
- generator = DatasetGenerator(config)
830
- results = generator.generate_all_datasets()
831
-
832
- print("\n" + "=" * 70)
833
- print("OUTPUT FILES")
834
- print("=" * 70)
835
- for dataset_name, filepath in results.items():
836
- print(f" {dataset_name}: {filepath}")
837
-
838
-
839
- if __name__ == "__main__":
840
- main()
 
1
+ """
2
+ ImmunoOrg 2.0: Dataset Generation for GRPO Training
3
+ =====================================================
4
+
5
+ Generates 1,200+ training scenarios optimized for GRPO training:
6
+ 1. Curriculum Learning (300 scenarios, Difficulty 1→4)
7
+ 2. Edge Case Coverage (400 scenarios, 12 failure modes)
8
+ 3. Balanced Complexity Matrix (300 scenarios, all difficulty×attack×org combos)
9
+ 4. Co-Evolution Progression (200 scenarios, adversary adaptation feedback)
10
+ """
11
+
12
+ import json
13
+ import gzip
14
+ import random
15
+ from typing import List, Dict, Any, Optional
16
+ from dataclasses import dataclass, asdict
17
+ from pathlib import Path
18
+ import logging
19
+
20
+ # Configure logging
21
+ logging.basicConfig(
22
+ level=logging.INFO,
23
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
24
+ )
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ # ============================================================
29
+ # DATA CLASSES & CONFIGS
30
+ # ============================================================
31
+
32
+ @dataclass
33
+ class DatasetConfig:
34
+ """Configuration for dataset generation."""
35
+ dataset_type: str # "curriculum" | "edge_case" | "complexity_matrix" | "coevolution"
36
+ output_dir: str = "training/datasets"
37
+ include_metadata: bool = True
38
+ compress_output: bool = True
39
+ verbose: bool = True
40
+
41
+
42
+ @dataclass
43
+ class ScenarioConfig:
44
+ """Environment configuration for a single scenario."""
45
+ difficulty: int
46
+ network_size: int
47
+ departments: int
48
+ silos: int
49
+ max_steps: int
50
+ attack_count: int
51
+ expected_reward_min: float
52
+ expected_reward_max: float
53
+ attack_vectors: List[str] = None
54
+ directives: List[str] = None
55
+ edge_case_type: Optional[str] = None
56
+ stage: Optional[str] = None
57
+ generation: Optional[int] = None
58
+
59
+
60
+ # ============================================================
61
+ # DATASET GENERATOR
62
+ # ============================================================
63
+
64
+ class DatasetGenerator:
65
+ """
66
+ Generates training datasets for GRPO training.
67
+
68
+ This class handles:
69
+ - Curriculum learning scenarios (progressive difficulty)
70
+ - Edge case scenarios (specific failure modes)
71
+ - Balanced complexity matrices (systematic coverage)
72
+ - Co-evolution scenarios (adversary adaptation)
73
+ """
74
+
75
+ # Curriculum configurations by difficulty level
76
+ CURRICULUM_CONFIGS = {
77
+ 1: {
78
+ "network_size": 7,
79
+ "departments": 3,
80
+ "silos": 0,
81
+ "max_steps": 50,
82
+ "attack_count": 1,
83
+ "expected_reward_min": 0.10,
84
+ "expected_reward_max": 0.20,
85
+ "attack_vectors": ["SQL_INJECTION", "XSS", "CREDENTIAL_STUFFING"],
86
+ "description": "Single-point attacks, simple blocking"
87
+ },
88
+ 2: {
89
+ "network_size": 12,
90
+ "departments": 4,
91
+ "silos": 0,
92
+ "max_steps": 100,
93
+ "attack_count": 2,
94
+ "expected_reward_min": 0.14,
95
+ "expected_reward_max": 0.30,
96
+ "attack_vectors": ["LATERAL_MOVEMENT", "PRIVILEGE_ESCALATION", "PHISHING"],
97
+ "description": "Multi-node with lateral spread, timeline reconstruction"
98
+ },
99
+ 3: {
100
+ "network_size": 18,
101
+ "departments": 6,
102
+ "silos": 2,
103
+ "max_steps": 150,
104
+ "attack_count": 3,
105
+ "expected_reward_min": 0.18,
106
+ "expected_reward_max": 0.35,
107
+ "attack_vectors": ["RANSOMWARE", "SUPPLY_CHAIN", "DDOS"],
108
+ "description": "Cascading failures + silos, org refactor needed"
109
+ },
110
+ 4: {
111
+ "network_size": 23,
112
+ "departments": 8,
113
+ "silos": 3,
114
+ "max_steps": 200,
115
+ "attack_count": 5,
116
+ "expected_reward_min": 0.22,
117
+ "expected_reward_max": 0.42,
118
+ "attack_vectors": ["APT_BACKDOOR", "ZERO_DAY", "LATERAL_MOVEMENT"],
119
+ "description": "APT with persistent backdoors, total restructuring"
120
+ }
121
+ }
122
+
123
+ # Edge case categories with scenario counts
124
+ EDGE_CASES = {
125
+ "war_room_deadlock": {"count": 40, "difficulties": [2, 3, 4]},
126
+ "silo_bottleneck": {"count": 40, "difficulties": [2, 3, 4]},
127
+ "false_positive": {"count": 35, "difficulties": [1, 2, 3]},
128
+ "stealth_attack": {"count": 35, "difficulties": [2, 3, 4]},
129
+ "cascading_failure": {"count": 35, "difficulties": [3, 4]},
130
+ "belief_divergence": {"count": 30, "difficulties": [2, 3, 4]},
131
+ "approval_confusion": {"count": 30, "difficulties": [1, 2, 3]},
132
+ "directive_conflict": {"count": 30, "difficulties": [2, 3, 4]},
133
+ "ransomware_spread": {"count": 30, "difficulties": [3, 4]},
134
+ "supply_chain": {"count": 30, "difficulties": [3, 4]},
135
+ "pipeline_breach": {"count": 25, "difficulties": [3, 4]},
136
+ "org_chart_ambiguity": {"count": 25, "difficulties": [2, 3, 4]},
137
+ }
138
+
139
+ # Complexity matrix dimensions
140
+ COMPLEXITY_DIMENSIONS = {
141
+ "difficulties": [1, 2, 3, 4],
142
+ "attack_vectors": [
143
+ "SQL_INJECTION", "XSS", "CREDENTIAL_STUFFING",
144
+ "LATERAL_MOVEMENT", "PRIVILEGE_ESCALATION",
145
+ "RANSOMWARE", "APT_BACKDOOR", "ZERO_DAY"
146
+ ],
147
+ "org_configs": [
148
+ {"depts": 3, "silos": 0},
149
+ {"depts": 4, "silos": 0},
150
+ {"depts": 6, "silos": 1},
151
+ {"depts": 6, "silos": 2},
152
+ {"depts": 8, "silos": 2},
153
+ {"depts": 8, "silos": 3}
154
+ ],
155
+ "directives": [None, "uptime_first", "security_first", "compliance_first", "conflicting"]
156
+ }
157
+
158
+ # Co-evolution adversary complexity by generation
159
+ COEVOLUTION_GENERATIONS = {
160
+ 0: {"stealth": 0.3, "vectors": 1, "adaptation": 0.0, "knowledge": 0.0},
161
+ 1: {"stealth": 0.5, "vectors": 2, "adaptation": 0.2, "knowledge": 0.1},
162
+ 2: {"stealth": 0.7, "vectors": 3, "adaptation": 0.4, "knowledge": 0.2},
163
+ 3: {"stealth": 0.8, "vectors": 4, "adaptation": 0.6, "knowledge": 0.3},
164
+ 4: {"stealth": 0.9, "vectors": 5, "adaptation": 0.8, "knowledge": 0.4}
165
+ }
166
+
167
+ def __init__(self, config: DatasetConfig):
168
+ """Initialize dataset generator with configuration."""
169
+ self.config = config
170
+ self.output_dir = Path(config.output_dir)
171
+ self.output_dir.mkdir(parents=True, exist_ok=True)
172
+ self.scenario_counter = 0
173
+
174
+ if config.verbose:
175
+ logger.info(f"Initialized DatasetGenerator")
176
+ logger.info(f"Output directory: {self.output_dir.absolute()}")
177
+
178
+ # ========================================================
179
+ # CURRICULUM LEARNING DATASET (300 scenarios)
180
+ # ========================================================
181
+
182
+ def generate_curriculum_dataset(self) -> List[Dict[str, Any]]:
183
+ """
184
+ Generate curriculum learning dataset: Difficulty 1 → 2 → 3 → 4.
185
+
186
+ Returns:
187
+ List of scenario dictionaries
188
+ """
189
+ logger.info("Generating Curriculum Learning Dataset (300 scenarios)...")
190
+ scenarios = []
191
+
192
+ for difficulty in [1, 2, 3, 4]:
193
+ config = self.CURRICULUM_CONFIGS[difficulty]
194
+ scenarios_for_difficulty = []
195
+ count = 75 # 75 scenarios per difficulty
196
+
197
+ for i in range(count):
198
+ self.scenario_counter += 1
199
+ scenario = {
200
+ "scenario_id": f"CL_L{difficulty}_{self.scenario_counter:03d}",
201
+ "dataset_type": "curriculum",
202
+ "difficulty": difficulty,
203
+ "stage": f"Level{difficulty}",
204
+ "stage_description": config["description"],
205
+ "seed": 100 + difficulty * 1000 + i,
206
+ "config": {
207
+ "network_size": config["network_size"],
208
+ "departments": config["departments"],
209
+ "silos": config["silos"],
210
+ "max_steps": config["max_steps"],
211
+ "attack_count": config["attack_count"],
212
+ "attack_vectors": config["attack_vectors"],
213
+ "expected_reward_range": [config["expected_reward_min"], config["expected_reward_max"]]
214
+ },
215
+ "metadata": {
216
+ "curriculum_stage": difficulty,
217
+ "requires_previous_success": difficulty > 1,
218
+ "recommended_training_epochs": 5 - difficulty + 1
219
+ }
220
+ }
221
+ scenarios_for_difficulty.append(scenario)
222
+
223
+ scenarios.extend(scenarios_for_difficulty)
224
+ logger.info(f" Difficulty {difficulty}: {count} scenarios")
225
+
226
+ logger.info(f"✓ Curriculum dataset complete: {len(scenarios)} scenarios")
227
+ return scenarios
228
+
229
+ # ========================================================
230
+ # EDGE CASE DATASET (400 scenarios)
231
+ # ========================================================
232
+
233
+ def generate_edge_case_dataset(self) -> List[Dict[str, Any]]:
234
+ """
235
+ Generate edge case scenarios covering 12 failure modes.
236
+
237
+ Returns:
238
+ List of scenario dictionaries
239
+ """
240
+ logger.info("Generating Edge Case Dataset (400 scenarios)...")
241
+ scenarios = []
242
+ total_scenarios = sum(cfg["count"] for cfg in self.EDGE_CASES.values())
243
+
244
+ for edge_case_type, edge_cfg in self.EDGE_CASES.items():
245
+ count = edge_cfg["count"]
246
+ difficulties = edge_cfg["difficulties"]
247
+
248
+ for i in range(count):
249
+ self.scenario_counter += 1
250
+ # Distribute scenarios across difficulties
251
+ difficulty = difficulties[i % len(difficulties)]
252
+
253
+ scenario = {
254
+ "scenario_id": f"EC_{edge_case_type.upper()}_{self.scenario_counter:03d}",
255
+ "dataset_type": "edge_case",
256
+ "edge_case_type": edge_case_type,
257
+ "difficulty": difficulty,
258
+ "seed": 2000 + self.scenario_counter,
259
+ "config": self._get_edge_case_config(edge_case_type, difficulty, i),
260
+ "metadata": {
261
+ "failure_mode": edge_case_type,
262
+ "expected_agent_challenge": "high",
263
+ "tests_robustness": True
264
+ }
265
+ }
266
+ scenarios.append(scenario)
267
+
268
+ logger.info(f" {edge_case_type}: {count} scenarios")
269
+
270
+ logger.info(f"✓ Edge case dataset complete: {len(scenarios)} scenarios")
271
+ return scenarios
272
+
273
+ def _get_edge_case_config(self, edge_case_type: str, difficulty: int, index: int) -> Dict[str, Any]:
274
+ """Get edge case-specific configuration."""
275
+ base_config = self.CURRICULUM_CONFIGS.get(difficulty, self.CURRICULUM_CONFIGS[1])
276
+
277
+ edge_case_specifics = {
278
+ "war_room_deadlock": {
279
+ "war_room_scenario": True,
280
+ "deadlock_turns": 6 + (difficulty - 1) * 2,
281
+ "personas_count": 3 + difficulty
282
+ },
283
+ "silo_bottleneck": {
284
+ "silos": max(1, difficulty - 1),
285
+ "approval_delays": [4 + difficulty, 3 + difficulty],
286
+ "requires_org_refactor": True
287
+ },
288
+ "false_positive": {
289
+ "decoy_attacks": 2 + difficulty,
290
+ "real_attack_clarity": 1.0 - (0.1 * difficulty)
291
+ },
292
+ "stealth_attack": {
293
+ "attack_stealth": 0.8 + (0.05 * difficulty),
294
+ "evasion_techniques": ["no_log_entries", "low_bandwidth", "mimic_legitimate_traffic"]
295
+ },
296
+ "cascading_failure": {
297
+ "cascading_enabled": True,
298
+ "failure_chain_length": 2 + difficulty,
299
+ "propagation_speed": 0.5 + (0.1 * difficulty)
300
+ },
301
+ "belief_divergence": {
302
+ "ground_truth_divergence": 0.4 + (0.1 * difficulty),
303
+ "agent_model_accuracy": 0.5
304
+ },
305
+ "approval_confusion": {
306
+ "authority_ambiguity": True,
307
+ "overlapping_depts": 2 + difficulty
308
+ },
309
+ "directive_conflict": {
310
+ "conflicting_directives": True,
311
+ "directive_count": 2 + difficulty
312
+ },
313
+ "ransomware_spread": {
314
+ "ransomware_nodes": 2 + difficulty,
315
+ "encryption_speed": 0.6 + (0.1 * difficulty)
316
+ },
317
+ "supply_chain": {
318
+ "external_attack": True,
319
+ "dependency_vulnerability": True
320
+ },
321
+ "pipeline_breach": {
322
+ "pipeline_gates": ["ast", "semantic", "terraform", "microvm"],
323
+ "gates_bypassed": min(3, difficulty)
324
+ },
325
+ "org_chart_ambiguity": {
326
+ "ambiguous_authority": True,
327
+ "overlapping_depts": 2
328
+ }
329
+ }
330
+
331
+ return {
332
+ **base_config,
333
+ **edge_case_specifics.get(edge_case_type, {})
334
+ }
335
+
336
+ # ========================================================
337
+ # COMPLEXITY MATRIX DATASET (300 scenarios)
338
+ # ========================================================
339
+
340
+ def generate_complexity_matrix_dataset(self) -> List[Dict[str, Any]]:
341
+ """
342
+ Generate balanced complexity matrix: uniform coverage of all
343
+ difficulty × attack × org_config × directive combinations.
344
+
345
+ Returns:
346
+ List of scenario dictionaries
347
+ """
348
+ logger.info("Generating Complexity Matrix Dataset (300 scenarios)...")
349
+ scenarios = []
350
+
351
+ # Calculate total combinations
352
+ total_combos = (
353
+ len(self.COMPLEXITY_DIMENSIONS["difficulties"]) *
354
+ len(self.COMPLEXITY_DIMENSIONS["attack_vectors"]) *
355
+ len(self.COMPLEXITY_DIMENSIONS["org_configs"]) *
356
+ len(self.COMPLEXITY_DIMENSIONS["directives"])
357
+ )
358
+ logger.info(f" Total possible combinations: {total_combos}")
359
+ logger.info(f" Sampling: 300 (Latin Hypercube stratification)")
360
+
361
+ # Latin Hypercube sampling for even coverage
362
+ samples_needed = 300
363
+ used_combos = set()
364
+
365
+ for i in range(samples_needed):
366
+ self.scenario_counter += 1
367
+
368
+ # Stratified random sampling
369
+ difficulty = random.choice(self.COMPLEXITY_DIMENSIONS["difficulties"])
370
+ attack = random.choice(self.COMPLEXITY_DIMENSIONS["attack_vectors"])
371
+ org_config = random.choice(self.COMPLEXITY_DIMENSIONS["org_configs"])
372
+ directive = random.choice(self.COMPLEXITY_DIMENSIONS["directives"])
373
+
374
+ combo_key = (difficulty, attack, org_config["depts"], org_config["silos"], directive)
375
+
376
+ scenario = {
377
+ "scenario_id": f"CM_{self.scenario_counter:03d}",
378
+ "dataset_type": "complexity_matrix",
379
+ "difficulty": difficulty,
380
+ "seed": 3000 + self.scenario_counter,
381
+ "matrix_position": {
382
+ "difficulty": difficulty,
383
+ "primary_attack": attack,
384
+ "org_depts": org_config["depts"],
385
+ "org_silos": org_config["silos"],
386
+ "directive_type": directive
387
+ },
388
+ "config": self._get_matrix_config(difficulty, attack, org_config, directive),
389
+ "metadata": {
390
+ "coverage_category": "balanced_sampling",
391
+ "ensures_generalization": True
392
+ }
393
+ }
394
+ scenarios.append(scenario)
395
+ used_combos.add(combo_key)
396
+
397
+ logger.info(f" Unique combinations covered: {len(used_combos)}/{total_combos}")
398
+ logger.info(f"✓ Complexity matrix dataset complete: {len(scenarios)} scenarios")
399
+ return scenarios
400
+
401
+ def _get_matrix_config(self, difficulty: int, attack: str, org_config: Dict, directive: Optional[str]) -> Dict[str, Any]:
402
+ """Get complexity matrix configuration."""
403
+ base_config = self.CURRICULUM_CONFIGS.get(difficulty, self.CURRICULUM_CONFIGS[1])
404
+
405
+ return {
406
+ "difficulty": difficulty,
407
+ "network_size": base_config["network_size"],
408
+ "departments": org_config["depts"],
409
+ "silos": org_config["silos"],
410
+ "max_steps": base_config["max_steps"],
411
+ "attack_vectors": [attack],
412
+ "attack_count": base_config["attack_count"],
413
+ "directive": directive,
414
+ "expected_reward_range": [base_config["expected_reward_min"], base_config["expected_reward_max"]]
415
+ }
416
+
417
+ # ========================================================
418
+ # CO-EVOLUTION DATASET (200 scenarios)
419
+ # ========================================================
420
+
421
+ def generate_coevolution_dataset(self) -> List[Dict[str, Any]]:
422
+ """
423
+ Generate co-evolution progression: adversary adapts over generations.
424
+
425
+ Returns:
426
+ List of scenario dictionaries
427
+ """
428
+ logger.info("Generating Co-Evolution Dataset (200 scenarios)...")
429
+ scenarios = []
430
+
431
+ generations = [0, 1, 2, 3, 4]
432
+ scenarios_per_gen = {0: 50, 1: 40, 2: 40, 3: 40, 4: 30}
433
+
434
+ for gen in generations:
435
+ count = scenarios_per_gen[gen]
436
+ adversary_complexity = self.COEVOLUTION_GENERATIONS[gen]
437
+
438
+ for i in range(count):
439
+ self.scenario_counter += 1
440
+
441
+ scenario = {
442
+ "scenario_id": f"COEV_G{gen}_{self.scenario_counter:03d}",
443
+ "dataset_type": "coevolution",
444
+ "generation": gen,
445
+ "difficulty": 2 + (gen // 2), # Difficulty increases with generation
446
+ "seed": 4000 + gen * 1000 + i,
447
+ "adversary_complexity": adversary_complexity,
448
+ "config": self._get_coevolution_config(gen),
449
+ "metadata": {
450
+ "adversary_stealth": adversary_complexity["stealth"],
451
+ "num_attack_vectors": adversary_complexity["vectors"],
452
+ "adaptation_speed": adversary_complexity["adaptation"],
453
+ "expected_difficulty": "increasing",
454
+ "tests_meta_learning": True
455
+ }
456
+ }
457
+ scenarios.append(scenario)
458
+
459
+ logger.info(f" Generation {gen}: {count} scenarios (stealth={adversary_complexity['stealth']:.1f})")
460
+
461
+ logger.info(f"✓ Co-evolution dataset complete: {len(scenarios)} scenarios")
462
+ return scenarios
463
+
464
+ def _get_coevolution_config(self, generation: int) -> Dict[str, Any]:
465
+ """Get co-evolution configuration for a generation."""
466
+ difficulty = min(4, 2 + (generation // 2))
467
+ base_config = self.CURRICULUM_CONFIGS[difficulty]
468
+
469
+ return {
470
+ "generation": generation,
471
+ "difficulty": difficulty,
472
+ "network_size": base_config["network_size"],
473
+ "departments": base_config["departments"],
474
+ "silos": base_config["silos"],
475
+ "max_steps": base_config["max_steps"],
476
+ "attack_count": base_config["attack_count"],
477
+ "attack_vectors": base_config["attack_vectors"],
478
+ "expected_reward_range": [
479
+ base_config["expected_reward_min"] + (generation * 0.05),
480
+ base_config["expected_reward_max"] + (generation * 0.08)
481
+ ]
482
+ }
483
+
484
+ # ========================================================
485
+ # ELITE JUDGE SCENARIO MIX (balanced conflict coverage)
486
+ # ========================================================
487
+
488
+ def generate_elite_scenario_mix_dataset(self, total: int = 500) -> List[Dict[str, Any]]:
489
+ """
490
+ Balanced mix of the 5 judge-facing training scenarios (20% each by default).
491
+
492
+ These scenarios are designed to be *conflict-heavy* (not random resets):
493
+ - RAG grounding (precision mitigation vs blunt isolation)
494
+ - Executive alignment / HITL (uptime directive forbids downtime-heavy tactics)
495
+ - Silo-breaker (org friction / repeated tactical denial)
496
+ - Stealth & persistence (multi-step investigation)
497
+ - Adaptive defense / co-evolution pressure (adversary adaptation ramps)
498
+
499
+ Notes:
500
+ - The `hooks` field is consumed by `training/trajectory_generator.py` via
501
+ `training/scenario_hooks.py` to shape rollouts beyond plain `reset()`.
502
+ """
503
+ if total % 5 != 0:
504
+ raise ValueError("total must be divisible by 5 for an even 20/20/20/20/20 split")
505
+
506
+ per = total // 5
507
+ logger.info(f"Generating Elite Scenario Mix Dataset ({total} scenarios = {per} each type)...")
508
+
509
+ families = [
510
+ ("basic_containment", "Phase1_BasicCompetence"),
511
+ ("rag_grounding", "Phase2_Intelligence"),
512
+ ("executive_alignment", "Phase3_Alignment"),
513
+ ("silo_breaker", "Phase4_Strategy"),
514
+ ("stealth_adaptive", "Phase5_Robustness"),
515
+ ]
516
+
517
+ scenarios: List[Dict[str, Any]] = []
518
+
519
+ def base_cfg(difficulty: int) -> Dict[str, Any]:
520
+ cfg = self.CURRICULUM_CONFIGS[difficulty]
521
+ return {
522
+ "network_size": cfg["network_size"],
523
+ "departments": cfg["departments"],
524
+ "silos": cfg["silos"],
525
+ "max_steps": cfg["max_steps"],
526
+ "attack_count": cfg["attack_count"],
527
+ "attack_vectors": cfg["attack_vectors"],
528
+ "expected_reward_range": [cfg["expected_reward_min"], cfg["expected_reward_max"]],
529
+ }
530
+
531
+ for family, phase in families:
532
+ for i in range(per):
533
+ self.scenario_counter += 1
534
+
535
+ if family == "basic_containment":
536
+ difficulty = 1 if i % 2 == 0 else 2
537
+ hooks: Dict[str, Any] = {}
538
+ elif family == "rag_grounding":
539
+ difficulty = 2 if i % 2 == 0 else 3
540
+ hooks = {
541
+ "inject_rag_best_mitigation": True,
542
+ "attack_vector": "APT_BACKDOOR",
543
+ "best_mitigation_chain": ["snapshot_forensics", "deploy_patch"],
544
+ }
545
+ elif family == "executive_alignment":
546
+ difficulty = 2 if i % 2 == 0 else 3
547
+ hooks = {"board_uptime_no_isolate": True}
548
+ elif family == "silo_breaker":
549
+ difficulty = 3 if i % 2 == 0 else 4
550
+ hooks = {"force_denials_on_isolate": True}
551
+ elif family == "stealth_adaptive":
552
+ difficulty = 3 if i % 2 == 0 else 4
553
+ hooks = {
554
+ "stealthy_initial_attack": True,
555
+ "stealth": 0.90 + (0.02 * (i % 3)),
556
+ "severity": 0.40 + (0.03 * (i % 4)),
557
+ "suppress_initial_logs": True,
558
+ }
559
+ # Within the final 20% bucket, alternate pure-stealth vs co-evolution pressure.
560
+ if i % 2 == 1:
561
+ hooks["boost_adversary_adaptation"] = True
562
+ hooks["adaptation_counter"] = 8 + (i % 10)
563
+ else:
564
+ # Should never happen, but keeps mypy/pyright happy in editors.
565
+ difficulty = 2
566
+ hooks = {}
567
+
568
+ scenario = {
569
+ "scenario_id": f"ELITE_{family.upper()}_{self.scenario_counter:04d}",
570
+ "dataset_type": "elite_scenario_mix",
571
+ "family": family,
572
+ "curriculum_phase": phase,
573
+ "difficulty": difficulty,
574
+ "seed": 9000 + self.scenario_counter,
575
+ "task": "curriculum_levels_1_to_4",
576
+ "config": base_cfg(difficulty),
577
+ "hooks": hooks,
578
+ "metadata": {
579
+ "judge_scenario_family": family,
580
+ "training_curriculum_phase": phase,
581
+ "mix_policy": "20/20/20/20/20",
582
+ },
583
+ }
584
+ scenarios.append(scenario)
585
+
586
+ logger.info(f"✓ Elite scenario mix complete: {len(scenarios)} scenarios")
587
+ return scenarios
588
+
589
+ # ========================================================
590
+ # ELITE JUDGE SCENARIO MIX (balanced conflict coverage)
591
+ # ========================================================
592
+
593
+ def generate_elite_scenario_mix_dataset(self, total: int = 500) -> List[Dict[str, Any]]:
594
+ """Balanced 20/20/20/20/20 mix of the 5 judge-facing training scenarios.
595
+
596
+ Families (consumed by ``training/scenario_hooks.py``):
597
+ - basic_containment : phase-appropriate baseline tactics
598
+ - rag_grounding : precise CVE mitigation vs blunt isolation
599
+ - executive_alignment : board uptime directive forbids isolation
600
+ - silo_breaker : approver always denies isolate; pivot to org
601
+ - stealth_adaptive : high-stealth attack + adversary adaptation pressure
602
+ """
603
+ if total % 5 != 0:
604
+ raise ValueError("total must be divisible by 5 for an even 20/20/20/20/20 split")
605
+
606
+ per = total // 5
607
+ logger.info(
608
+ f"Generating Elite Scenario Mix Dataset ({total} scenarios = {per} each family)..."
609
+ )
610
+
611
+ families = [
612
+ ("basic_containment", "Phase1_BasicCompetence"),
613
+ ("rag_grounding", "Phase2_Intelligence"),
614
+ ("executive_alignment", "Phase3_Alignment"),
615
+ ("silo_breaker", "Phase4_Strategy"),
616
+ ("stealth_adaptive", "Phase5_Robustness"),
617
+ ]
618
+
619
+ def base_cfg(difficulty: int) -> Dict[str, Any]:
620
+ cfg = self.CURRICULUM_CONFIGS[difficulty]
621
+ return {
622
+ "network_size": cfg["network_size"],
623
+ "departments": cfg["departments"],
624
+ "silos": cfg["silos"],
625
+ "max_steps": cfg["max_steps"],
626
+ "attack_count": cfg["attack_count"],
627
+ "attack_vectors": cfg["attack_vectors"],
628
+ "expected_reward_range": [
629
+ cfg["expected_reward_min"],
630
+ cfg["expected_reward_max"],
631
+ ],
632
+ }
633
+
634
+ scenarios: List[Dict[str, Any]] = []
635
+ for family, phase in families:
636
+ for i in range(per):
637
+ self.scenario_counter += 1
638
+
639
+ if family == "basic_containment":
640
+ difficulty = 1 if i % 2 == 0 else 2
641
+ hooks: Dict[str, Any] = {}
642
+ elif family == "rag_grounding":
643
+ difficulty = 2 if i % 2 == 0 else 3
644
+ hooks = {
645
+ "inject_rag_best_mitigation": True,
646
+ "attack_vector": "apt_backdoor",
647
+ "best_mitigation_chain": ["snapshot_forensics", "deploy_patch"],
648
+ }
649
+ elif family == "executive_alignment":
650
+ difficulty = 2 if i % 2 == 0 else 3
651
+ hooks = {"board_uptime_no_isolate": True}
652
+ elif family == "silo_breaker":
653
+ difficulty = 3 if i % 2 == 0 else 4
654
+ hooks = {"force_denials_on_isolate": True}
655
+ elif family == "stealth_adaptive":
656
+ difficulty = 3 if i % 2 == 0 else 4
657
+ hooks = {
658
+ "stealthy_initial_attack": True,
659
+ "stealth": 0.90 + (0.02 * (i % 3)),
660
+ "severity": 0.40 + (0.03 * (i % 4)),
661
+ "suppress_initial_logs": True,
662
+ }
663
+ if i % 2 == 1:
664
+ hooks["boost_adversary_adaptation"] = True
665
+ hooks["adaptation_counter"] = 8 + (i % 10)
666
+ else:
667
+ difficulty, hooks = 2, {}
668
+
669
+ scenarios.append({
670
+ "scenario_id": f"ELITE_{family.upper()}_{self.scenario_counter:04d}",
671
+ "dataset_type": "elite_scenario_mix",
672
+ "family": family,
673
+ "curriculum_phase": phase,
674
+ "difficulty": difficulty,
675
+ "seed": 9000 + self.scenario_counter,
676
+ "task": "curriculum_levels_1_to_4",
677
+ "config": base_cfg(difficulty),
678
+ "hooks": hooks,
679
+ "metadata": {
680
+ "judge_scenario_family": family,
681
+ "training_curriculum_phase": phase,
682
+ "mix_policy": "20/20/20/20/20",
683
+ },
684
+ })
685
+
686
+ logger.info(f"Elite scenario mix complete: {len(scenarios)} scenarios")
687
+ return scenarios
688
+
689
+ # ========================================================
690
+ # SAVE METHODS
691
+ # ========================================================
692
+
693
+ def save_dataset(self, scenarios: List[Dict[str, Any]], filename: str) -> str:
694
+ """
695
+ Save dataset to file (optionally compressed).
696
+
697
+ Args:
698
+ scenarios: List of scenario dictionaries
699
+ filename: Output filename
700
+
701
+ Returns:
702
+ Path to saved file
703
+ """
704
+ output_path = self.output_dir / filename
705
+
706
+ if self.config.compress_output and filename.endswith('.json'):
707
+ output_path = output_path.with_suffix('.json.gz')
708
+ with gzip.open(str(output_path), 'wt', encoding='utf-8') as f:
709
+ json.dump(scenarios, f, indent=2)
710
+ logger.info(f"Saved {len(scenarios)} scenarios to {output_path} (compressed)")
711
+ else:
712
+ with open(output_path, 'w', encoding='utf-8') as f:
713
+ json.dump(scenarios, f, indent=2)
714
+ logger.info(f"Saved {len(scenarios)} scenarios to {output_path}")
715
+
716
+ return str(output_path)
717
+
718
+ # ========================================================
719
+ # MAIN GENERATION METHOD
720
+ # ========================================================
721
+
722
+ def generate_all_datasets(self) -> Dict[str, str]:
723
+ """
724
+ Generate all core dataset types plus the elite judge scenario mix.
725
+
726
+ Returns:
727
+ Dictionary mapping dataset names to file paths
728
+ """
729
+ logger.info("=" * 70)
730
+ logger.info("STARTING COMPLETE DATASET GENERATION")
731
+ logger.info("=" * 70)
732
+
733
+ results = {}
734
+
735
+ # 1. Curriculum Learning
736
+ logger.info("\n[1/4] CURRICULUM LEARNING DATASET")
737
+ curriculum_scenarios = self.generate_curriculum_dataset()
738
+ curriculum_path = self.save_dataset(curriculum_scenarios, "curriculum_dataset.json")
739
+ results["curriculum"] = curriculum_path
740
+
741
+ # 2. Edge Cases
742
+ logger.info("\n[2/4] EDGE CASE DATASET")
743
+ edge_case_scenarios = self.generate_edge_case_dataset()
744
+ edge_case_path = self.save_dataset(edge_case_scenarios, "edge_case_dataset.json")
745
+ results["edge_case"] = edge_case_path
746
+
747
+ # 3. Complexity Matrix
748
+ logger.info("\n[3/4] COMPLEXITY MATRIX DATASET")
749
+ matrix_scenarios = self.generate_complexity_matrix_dataset()
750
+ matrix_path = self.save_dataset(matrix_scenarios, "complexity_matrix_dataset.json")
751
+ results["complexity_matrix"] = matrix_path
752
+
753
+ # 4. Co-Evolution
754
+ logger.info("\n[4/5] CO-EVOLUTION DATASET")
755
+ coevolution_scenarios = self.generate_coevolution_dataset()
756
+ coevolution_path = self.save_dataset(coevolution_scenarios, "coevolution_dataset.json")
757
+ results["coevolution"] = coevolution_path
758
+
759
+ # 5. Elite judge scenario mix (balanced conflict coverage)
760
+ logger.info("\n[5/5] ELITE SCENARIO MIX (JUDGE-TIER)")
761
+ elite_scenarios = self.generate_elite_scenario_mix_dataset(total=500)
762
+ elite_path = self.save_dataset(elite_scenarios, "elite_scenario_mix_dataset.json")
763
+ results["elite_scenario_mix"] = elite_path
764
+
765
+ # Summary
766
+ logger.info("\n" + "=" * 70)
767
+ logger.info("DATASET GENERATION COMPLETE")
768
+ logger.info("=" * 70)
769
+ self._print_summary(
770
+ curriculum_scenarios,
771
+ edge_case_scenarios,
772
+ matrix_scenarios,
773
+ coevolution_scenarios,
774
+ elite_scenarios,
775
+ )
776
+
777
+ return results
778
+
779
+ def _print_summary(self, curriculum, edge_cases, matrix, coevolution, elite_mix):
780
+ """Print a formatted summary of generated datasets."""
781
+ total_scenarios = len(curriculum) + len(edge_cases) + len(matrix) + len(coevolution) + len(elite_mix)
782
+
783
+ summary = f"""
784
+ DATASET GENERATION SUMMARY
785
+ ==========================
786
+
787
+ Curriculum Learning Dataset:
788
+ - Total scenarios: {len(curriculum)}
789
+ - Difficulty 1: {len([s for s in curriculum if s['difficulty'] == 1])} scenarios
790
+ - Difficulty 2: {len([s for s in curriculum if s['difficulty'] == 2])} scenarios
791
+ - Difficulty 3: {len([s for s in curriculum if s['difficulty'] == 3])} scenarios
792
+ - Difficulty 4: {len([s for s in curriculum if s['difficulty'] == 4])} scenarios
793
+
794
+ Edge Case Dataset:
795
+ - Total scenarios: {len(edge_cases)}
796
+ - Coverage: {len(self.EDGE_CASES)} failure modes
797
+
798
+ Complexity Matrix Dataset:
799
+ - Total scenarios: {len(matrix)}
800
+ - Coverage: {len(set((s['matrix_position']['difficulty'], s['matrix_position']['primary_attack']) for s in matrix))} difficulty×attack combos
801
+
802
+ Co-Evolution Dataset:
803
+ - Total scenarios: {len(coevolution)}
804
+ - Generations: {len(set(s['generation'] for s in coevolution))}
805
+
806
+ Elite Scenario Mix (Judge-tier):
807
+ - Total scenarios: {len(elite_mix)}
808
+ - Families: {len(set(s.get('family') for s in elite_mix))}
809
+
810
+ GRAND TOTAL: {total_scenarios} scenarios across all datasets
811
+ """
812
+ logger.info(summary)
813
+
814
+
815
+ # ============================================================
816
+ # CLI UTILITY
817
+ # ============================================================
818
+
819
+ def main():
820
+ """Generate all datasets (CLI entry point)."""
821
+ config = DatasetConfig(
822
+ dataset_type="all",
823
+ output_dir="training/datasets",
824
+ include_metadata=True,
825
+ compress_output=True,
826
+ verbose=True
827
+ )
828
+
829
+ generator = DatasetGenerator(config)
830
+ results = generator.generate_all_datasets()
831
+
832
+ print("\n" + "=" * 70)
833
+ print("OUTPUT FILES")
834
+ print("=" * 70)
835
+ for dataset_name, filepath in results.items():
836
+ print(f" {dataset_name}: {filepath}")
837
+
838
+
839
+ if __name__ == "__main__":
840
+ main()