fable-engine 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. fable_compressor.py +356 -0
  2. fable_engine/__init__.py +1 -0
  3. fable_engine/actions/__init__.py +291 -0
  4. fable_engine/actions/cas.py +182 -0
  5. fable_engine/actions/deliberation.py +523 -0
  6. fable_engine/actions/fleet.py +807 -0
  7. fable_engine/actions/lifecycle.py +298 -0
  8. fable_engine/actions/scrapers.py +116 -0
  9. fable_engine/actions/system3.py +815 -0
  10. fable_engine/browser.py +824 -0
  11. fable_engine/cas.py +974 -0
  12. fable_engine/fable_session.json +510 -0
  13. fable_engine/guards.py +283 -0
  14. fable_engine/schema.py +714 -0
  15. fable_engine/scrapers/__init__.py +32 -0
  16. fable_engine/scrapers/arxiv.py +115 -0
  17. fable_engine/scrapers/base.py +386 -0
  18. fable_engine/scrapers/github.py +129 -0
  19. fable_engine/scrapers/reddit.py +154 -0
  20. fable_engine/scrapers/web.py +120 -0
  21. fable_engine/scrapers/x.py +125 -0
  22. fable_engine/scrapers/youtube.py +132 -0
  23. fable_engine/server.py +414 -0
  24. fable_engine/session.py +1819 -0
  25. fable_engine/test_server.py +1362 -0
  26. fable_engine/updater.py +541 -0
  27. fable_engine-1.3.1.dist-info/LICENSE +22 -0
  28. fable_engine-1.3.1.dist-info/METADATA +173 -0
  29. fable_engine-1.3.1.dist-info/RECORD +104 -0
  30. fable_engine-1.3.1.dist-info/WHEEL +5 -0
  31. fable_engine-1.3.1.dist-info/entry_points.txt +5 -0
  32. fable_engine-1.3.1.dist-info/top_level.txt +6 -0
  33. fable_mode/__init__.py +3 -0
  34. fable_mode/__main__.py +4 -0
  35. fable_mode/adapters.py +1014 -0
  36. fable_mode/installer.py +553 -0
  37. fable_mode/launcher.py +437 -0
  38. fable_mode/manifest.py +142 -0
  39. fable_mode/resources.json +114 -0
  40. fable_mode/safety.py +103 -0
  41. fable_mode_entry.py +10 -0
  42. fable_v2/__init__.py +146 -0
  43. fable_v2/adapters.py +151 -0
  44. fable_v2/coder_fleet/__init__.py +100 -0
  45. fable_v2/coder_fleet/ast_tools.py +158 -0
  46. fable_v2/coder_fleet/compute.py +199 -0
  47. fable_v2/coder_fleet/design_engine.py +1316 -0
  48. fable_v2/coder_fleet/diagnostics.py +293 -0
  49. fable_v2/coder_fleet/fleet_dispatcher.py +214 -0
  50. fable_v2/coder_fleet/mock_auditor.py +306 -0
  51. fable_v2/coder_fleet/mutation.py +216 -0
  52. fable_v2/coder_fleet/property_oracle.py +260 -0
  53. fable_v2/coder_fleet/receipt_attestor.py +122 -0
  54. fable_v2/coder_fleet/red_team_swarm.py +908 -0
  55. fable_v2/coder_fleet/test_harness.py +198 -0
  56. fable_v2/coder_fleet/vector_engine.py +1287 -0
  57. fable_v2/coder_fleet/visual.py +357 -0
  58. fable_v2/coder_fleet/workspace.py +153 -0
  59. fable_v2/cortical/__init__.py +20 -0
  60. fable_v2/cortical/plasticity_engine.py +992 -0
  61. fable_v2/execution_broker.py +811 -0
  62. fable_v2/proof_engine.py +1141 -0
  63. fable_v2/protocol.py +485 -0
  64. fable_v2/runtime.py +1010 -0
  65. fable_v2/system3/__init__.py +204 -0
  66. fable_v2/system3/causal.py +558 -0
  67. fable_v2/system3/dialectical.py +577 -0
  68. fable_v2/system3/evolution.py +503 -0
  69. fable_v2/system3/executive.py +338 -0
  70. fable_v2/system3/free_energy.py +479 -0
  71. fable_v2/system3/hyperbolic.py +555 -0
  72. fable_v2/system3/induction.py +336 -0
  73. fable_v2/system3/kripke.py +548 -0
  74. fable_v2/system3/oracle.py +745 -0
  75. fable_v2/verifiers.py +72 -0
  76. tests/__init__.py +1 -0
  77. tests/test_anti_loop_circuit_breaker.py +64 -0
  78. tests/test_auto_updater.py +407 -0
  79. tests/test_coder_fleet.py +535 -0
  80. tests/test_delegation_compiler.py +54 -0
  81. tests/test_descriptor_boundaries.py +126 -0
  82. tests/test_design_engine.py +603 -0
  83. tests/test_epistemic_evidence_validator.py +66 -0
  84. tests/test_execution_broker.py +233 -0
  85. tests/test_fable_v2.py +406 -0
  86. tests/test_fleet_transitions.py +116 -0
  87. tests/test_fsm_redteam_evolution.py +406 -0
  88. tests/test_goal_rubric_and_pipeline.py +367 -0
  89. tests/test_hebbian_plasticity.py +585 -0
  90. tests/test_packaging_runtime.py +194 -0
  91. tests/test_proof_engine.py +259 -0
  92. tests/test_red_team_swarm.py +645 -0
  93. tests/test_redteam_remediation.py +169 -0
  94. tests/test_registration_transaction.py +375 -0
  95. tests/test_requested_regressions.py +467 -0
  96. tests/test_scrapers.py +370 -0
  97. tests/test_server_actions.py +93 -0
  98. tests/test_server_frontier_actions.py +269 -0
  99. tests/test_server_protocol.py +88 -0
  100. tests/test_stealth_browser.py +970 -0
  101. tests/test_system3.py +381 -0
  102. tests/test_system3_deep_integration.py +385 -0
  103. tests/test_system3_frontier.py +436 -0
  104. tests/test_vector_engine.py +608 -0
@@ -0,0 +1,535 @@
1
+ """Comprehensive Unit Test Suite for the 10-Tool Coder Subagent MCP Fleet.
2
+
3
+ Tests all engines:
4
+ - VisualGroundingEngine
5
+ - DiagnosticsEngine
6
+ - TreeSitterCodemodEngine
7
+ - AtomicWorkspaceEngine
8
+ - TestHarnessEngine
9
+ - MutationVerifierEngine
10
+ - MockAuditorEngine
11
+ - PropertyOracleEngine
12
+ - ReceiptAttestorEngine
13
+ - ComputeOrchestratorEngine
14
+ - CoderFleetDispatcher
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import sys
19
+ import unittest
20
+ from pathlib import Path
21
+
22
+ # Ensure workspace root is in sys.path
23
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
24
+
25
+ from fable_v2.coder_fleet import (
26
+ AtomicWorkspaceEngine,
27
+ CoderFleetDispatcher,
28
+ ComputeOrchestratorEngine,
29
+ DiagnosticsEngine,
30
+ MockAuditorEngine,
31
+ MutationVerifierEngine,
32
+ PropertyOracleEngine,
33
+ ReceiptAttestorEngine,
34
+ TestHarnessEngine,
35
+ TreeSitterCodemodEngine,
36
+ VisualGroundingEngine,
37
+ )
38
+
39
+
40
+ class TestVisualGroundingEngine(unittest.TestCase):
41
+ def setUp(self) -> None:
42
+ self.engine = VisualGroundingEngine()
43
+
44
+ def test_render_vector_valid(self) -> None:
45
+ svg_code = (
46
+ '<svg viewBox="0 0 100 100" width="100" height="100">'
47
+ ' <rect x="10" y="10" width="50" height="50" fill="#ff0000"/>'
48
+ ' <circle cx="50" cy="50" r="25" stroke="#00ff00"/>'
49
+ ' <path d="M 10 10 L 90 90 Z" />'
50
+ "</svg>"
51
+ )
52
+ res = self.engine.render_vector(svg_code)
53
+ self.assertTrue(res["valid"])
54
+ self.assertEqual(res["viewBox"], [0.0, 0.0, 100.0, 100.0])
55
+ self.assertEqual(res["width"], "100")
56
+ self.assertEqual(res["height"], "100")
57
+ self.assertIn("rect", res["element_types"])
58
+ self.assertIn("circle", res["element_types"])
59
+ self.assertIn("path", res["element_types"])
60
+ self.assertEqual(len(res["paths"]), 1)
61
+ self.assertGreaterEqual(res["metadata"]["total_path_commands"], 3)
62
+
63
+ def test_render_vector_invalid(self) -> None:
64
+ res = self.engine.render_vector("<svg><unclosed></svg>")
65
+ self.assertFalse(res["valid"])
66
+ self.assertIn("error", res)
67
+
68
+ empty_res = self.engine.render_vector("")
69
+ self.assertFalse(empty_res["valid"])
70
+
71
+ def test_perceptual_diff(self) -> None:
72
+ svg_code = (
73
+ '<svg viewBox="0 0 200 200">'
74
+ ' <rect x="0" y="0" width="100" height="100" fill="#123456"/>'
75
+ ' <path d="M 0 0 L 100 100"/>'
76
+ "</svg>"
77
+ )
78
+ target_spec = {
79
+ "expected_types": ["rect", "path"],
80
+ "palette": ["#123456"],
81
+ "min_elements": 2,
82
+ "min_paths": 1,
83
+ "viewBox": [0.0, 0.0, 200.0, 200.0],
84
+ }
85
+ res = self.engine.perceptual_diff(svg_code, target_spec)
86
+ self.assertGreaterEqual(res["similarity_score"], 0.9)
87
+ self.assertEqual(res["coverage"], 1.0)
88
+ self.assertEqual(len(res["diff_details"]), 0)
89
+
90
+ def test_extract_palette_and_boxes(self) -> None:
91
+ svg_code = (
92
+ '<svg viewBox="0 0 100 100">'
93
+ ' <rect x="10" y="20" width="30" height="40" fill="#abcdef" stroke="rgb(0, 128, 255)"/>'
94
+ ' <circle cx="60" cy="70" r="15" style="fill: oklch(0.7 0.15 150); stroke: #ffffff"/>'
95
+ "</svg>"
96
+ )
97
+ res = self.engine.extract_palette_and_boxes(svg_code)
98
+ palette = res["palette"]
99
+ self.assertIn("#abcdef", palette["fills"])
100
+ self.assertIn("#ffffff", palette["strokes"])
101
+ self.assertTrue(any("oklch" in c.lower() for c in palette["all_colors"]))
102
+
103
+ boxes = res["bounding_boxes"]
104
+ self.assertEqual(len(boxes), 2)
105
+ rect_box = [b for b in boxes if b["tag"] == "rect"][0]
106
+ self.assertEqual(rect_box["x"], 10.0)
107
+ self.assertEqual(rect_box["y"], 20.0)
108
+ self.assertEqual(rect_box["width"], 30.0)
109
+ self.assertEqual(rect_box["height"], 40.0)
110
+
111
+
112
+ class TestDiagnosticsEngine(unittest.TestCase):
113
+ def setUp(self) -> None:
114
+ self.engine = DiagnosticsEngine()
115
+
116
+ def test_run_diagnostics_syntax_error(self) -> None:
117
+ bad_code = "def broken(\n return 1"
118
+ diags = self.engine.run_diagnostics(bad_code)
119
+ self.assertEqual(len(diags), 1)
120
+ self.assertEqual(diags[0]["code"], "E999")
121
+ self.assertEqual(diags[0]["severity"], "error")
122
+
123
+ def test_run_diagnostics_unused_import(self) -> None:
124
+ code = "import os\n\ndef run():\n return 42\n"
125
+ diags = self.engine.run_diagnostics(code)
126
+ unused = [d for d in diags if d["code"] == "W0611"]
127
+ self.assertEqual(len(unused), 1)
128
+ self.assertEqual(unused[0]["symbol"], "os")
129
+
130
+ def test_run_diagnostics_undefined_name(self) -> None:
131
+ code = "def compute():\n return unknown_symbol * 2\n"
132
+ diags = self.engine.run_diagnostics(code)
133
+ undef = [d for d in diags if d["code"] == "E0602"]
134
+ self.assertEqual(len(undef), 1)
135
+ self.assertEqual(undef[0]["symbol"], "unknown_symbol")
136
+
137
+ def test_run_diagnostics_bare_except(self) -> None:
138
+ code = "try:\n x = 1\nexcept:\n x = 0\n"
139
+ diags = self.engine.run_diagnostics(code)
140
+ bare = [d for d in diags if d["code"] == "W0702"]
141
+ self.assertEqual(len(bare), 1)
142
+
143
+ def test_apply_quick_fix_unused_import(self) -> None:
144
+ code = "import os\n\ndef run():\n return 42\n"
145
+ diags = self.engine.run_diagnostics(code)
146
+ unused_diag = [d for d in diags if d["code"] == "W0611"][0]
147
+ fixed = self.engine.apply_quick_fix(unused_diag, code)
148
+ self.assertNotIn("import os", fixed)
149
+
150
+ def test_apply_quick_fix_undefined_name(self) -> None:
151
+ code = "def calc():\n return math.sqrt(16)\n"
152
+ diags = self.engine.run_diagnostics(code)
153
+ undef_diag = [d for d in diags if d["code"] == "E0602" and d["symbol"] == "math"][0]
154
+ fixed = self.engine.apply_quick_fix(undef_diag, code)
155
+ self.assertIn("import math", fixed)
156
+
157
+ def test_apply_quick_fix_bare_except(self) -> None:
158
+ code = "try:\n x = 1\nexcept:\n x = 0\n"
159
+ diags = self.engine.run_diagnostics(code)
160
+ bare_diag = [d for d in diags if d["code"] == "W0702"][0]
161
+ fixed = self.engine.apply_quick_fix(bare_diag, code)
162
+ self.assertIn("except Exception:", fixed)
163
+
164
+
165
+ class TestTreeSitterCodemodEngine(unittest.TestCase):
166
+ def setUp(self) -> None:
167
+ self.engine = TreeSitterCodemodEngine()
168
+
169
+ def test_query_ast_functions(self) -> None:
170
+ code = (
171
+ "def add(a: int, b: int = 0) -> int:\n"
172
+ ' """Add two numbers."""\n'
173
+ " return a + b\n"
174
+ )
175
+ nodes = self.engine.query_ast(code, node_type="FunctionDef")
176
+ self.assertEqual(len(nodes), 1)
177
+ self.assertEqual(nodes[0]["name"], "add")
178
+ self.assertEqual(nodes[0]["args"], ["a", "b"])
179
+ self.assertEqual(nodes[0]["docstring"], "Add two numbers.")
180
+
181
+ def test_rename_symbol_safe(self) -> None:
182
+ source = (
183
+ "def calculate(value):\n"
184
+ " # Note: calculate is fast\n"
185
+ ' msg = "do not calculate this calculate"\n'
186
+ " return calculate(value - 1) if value > 0 else 0\n"
187
+ )
188
+ new_code, count = self.engine.rename_symbol(source, "calculate", "compute")
189
+ self.assertEqual(count, 2) # Function definition and recursive call
190
+ self.assertIn("def compute(value):", new_code)
191
+ self.assertIn("return compute(value - 1)", new_code)
192
+ # Verify comment and string were NOT touched
193
+ self.assertIn("# Note: calculate is fast", new_code)
194
+ self.assertIn('"do not calculate this calculate"', new_code)
195
+
196
+ def test_verify_syntax(self) -> None:
197
+ valid_py = "x = 42\n"
198
+ invalid_py = "x = (\n"
199
+ self.assertTrue(self.engine.verify_syntax(valid_py, "python")["valid"])
200
+ self.assertFalse(self.engine.verify_syntax(invalid_py, "python")["valid"])
201
+
202
+ valid_json = '{"a": 1, "b": [2, 3]}'
203
+ invalid_json = '{"a": 1,}'
204
+ self.assertTrue(self.engine.verify_syntax(valid_json, "json")["valid"])
205
+ self.assertFalse(self.engine.verify_syntax(invalid_json, "json")["valid"])
206
+
207
+
208
+ class TestAtomicWorkspaceEngine(unittest.TestCase):
209
+ def setUp(self) -> None:
210
+ self.engine = AtomicWorkspaceEngine()
211
+
212
+ def test_checkpoint_inspect_and_rollback(self) -> None:
213
+ initial_files = {
214
+ "main.py": "def main():\n print('hello')\n",
215
+ "utils.py": "def util():\n return 1\n",
216
+ }
217
+ chk_id = self.engine.create_checkpoint("task-101", initial_files)
218
+ self.assertTrue(chk_id.startswith("chk_task-101_"))
219
+
220
+ # Modify files
221
+ current_files = {
222
+ "main.py": "def main():\n print('world')\n", # modified
223
+ "new.py": "NEW = True\n", # added
224
+ # utils.py deleted
225
+ }
226
+
227
+ patch = self.engine.inspect_patch(chk_id, current_files)
228
+ self.assertEqual(patch["files_changed"], 3)
229
+ self.assertEqual(patch["details"]["main.py"]["status"], "modified")
230
+ self.assertEqual(patch["details"]["new.py"]["status"], "added")
231
+ self.assertEqual(patch["details"]["utils.py"]["status"], "deleted")
232
+
233
+ # Rollback
234
+ restored = self.engine.rollback(chk_id)
235
+ self.assertEqual(restored, initial_files)
236
+
237
+ def test_commit_milestone(self) -> None:
238
+ files = {"config.json": '{"v": 1}'}
239
+ chk_id = self.engine.create_checkpoint("milestone-task", files)
240
+ milestone = self.engine.commit_milestone(chk_id, "Initial baseline commit")
241
+ self.assertEqual(milestone["checkpoint_id"], chk_id)
242
+ self.assertEqual(milestone["message"], "Initial baseline commit")
243
+ self.assertEqual(milestone["status"], "committed")
244
+ self.assertEqual(len(milestone["digest"]), 64)
245
+
246
+
247
+ class TestTestHarnessEngine(unittest.TestCase):
248
+ def setUp(self) -> None:
249
+ self.engine = TestHarnessEngine()
250
+
251
+ def test_run_scratch_test_success(self) -> None:
252
+ code = "print('harness_success')\n"
253
+ res = self.engine.run_scratch_test(code, timeout_sec=3.0)
254
+ self.assertTrue(res["success"])
255
+ self.assertEqual(res["returncode"], 0)
256
+ self.assertIn("harness_success", res["stdout"])
257
+ self.assertFalse(res["timed_out"])
258
+
259
+ def test_run_scratch_test_timeout(self) -> None:
260
+ code = "import time\ntime.sleep(2.0)\n"
261
+ res = self.engine.run_scratch_test(code, timeout_sec=0.2)
262
+ self.assertFalse(res["success"])
263
+ self.assertTrue(res["timed_out"])
264
+ self.assertEqual(res["returncode"], -1)
265
+
266
+ def test_concurrency_fuzz(self) -> None:
267
+ code = (
268
+ "counter = 0\n"
269
+ "def target_fn():\n"
270
+ " global counter\n"
271
+ " counter += 1\n"
272
+ )
273
+ res = self.engine.concurrency_fuzz(code, threads=2, iterations=20)
274
+ self.assertTrue(res["success"])
275
+ self.assertFalse(res["race_conditions_detected"])
276
+
277
+ def test_profile_memory_and_cpu(self) -> None:
278
+ code = "data = [i ** 2 for i in range(10000)]\n"
279
+ res = self.engine.profile_memory_and_cpu(code)
280
+ self.assertTrue(res["success"])
281
+ self.assertGreater(res["peak_memory_bytes"], 0)
282
+ self.assertGreaterEqual(res["duration_ms"], 0.0)
283
+
284
+
285
+ class TestMutationVerifierEngine(unittest.TestCase):
286
+ def setUp(self) -> None:
287
+ self.engine = MutationVerifierEngine()
288
+
289
+ def test_inject_mutants(self) -> None:
290
+ code = (
291
+ "def check_value(x: int) -> bool:\n"
292
+ " if x > 10:\n"
293
+ " return True\n"
294
+ " return False\n"
295
+ )
296
+ mutants = self.engine.inject_mutants(code)
297
+ self.assertGreaterEqual(len(mutants), 3)
298
+ self.assertLessEqual(len(mutants), 5)
299
+ for m in mutants:
300
+ self.assertIn("mutant_id", m)
301
+ self.assertIn("mutated_code", m)
302
+ self.assertIn("mutation_type", m)
303
+
304
+ def test_audit_test_strength_kills_mutants(self) -> None:
305
+ source_code = (
306
+ "def is_positive(x: int) -> bool:\n"
307
+ " if x > 0:\n"
308
+ " return True\n"
309
+ " return False\n"
310
+ )
311
+ # Strong test suite that tests > 0 thoroughly
312
+ test_code = (
313
+ "assert is_positive(5) is True\n"
314
+ "assert is_positive(0) is False\n"
315
+ "assert is_positive(-5) is False\n"
316
+ )
317
+ res = self.engine.audit_test_strength(source_code, test_code)
318
+ self.assertGreater(res["mutants_total"], 0)
319
+ self.assertGreater(res["mutants_killed"], 0)
320
+ self.assertGreaterEqual(res["mutation_score"], 0.5)
321
+
322
+ def test_audit_test_strength_detects_fake_tests(self) -> None:
323
+ source_code = (
324
+ "def is_positive(x: int) -> bool:\n"
325
+ " if x > 0:\n"
326
+ " return True\n"
327
+ " return False\n"
328
+ )
329
+ # Fake test that does not actually check function output
330
+ fake_test = "assert True\nassert 1 == 1\n"
331
+ res = self.engine.audit_test_strength(source_code, fake_test)
332
+ self.assertEqual(res["mutants_killed"], 0)
333
+ self.assertEqual(res["mutation_score"], 0.0)
334
+ self.assertFalse(res["is_thorough"])
335
+
336
+
337
+ class TestMockAuditorEngine(unittest.TestCase):
338
+ def setUp(self) -> None:
339
+ self.engine = MockAuditorEngine()
340
+
341
+ def test_audit_assertions_flags_tautologies(self) -> None:
342
+ test_code = (
343
+ "def test_fake():\n"
344
+ " assert True\n"
345
+ " assert 1 == 1\n"
346
+ " self.assertTrue(True)\n"
347
+ )
348
+ res = self.engine.audit_assertions(test_code)
349
+ self.assertFalse(res["passed_audit"])
350
+ self.assertGreaterEqual(res["trivial_assertions_count"], 3)
351
+ self.assertEqual(len(res["tautologies"]), res["trivial_assertions_count"])
352
+
353
+ def test_audit_assertions_passes_clean_test(self) -> None:
354
+ clean_code = (
355
+ "def test_real():\n"
356
+ " result = 2 + 2\n"
357
+ " assert result == 4\n"
358
+ )
359
+ res = self.engine.audit_assertions(clean_code)
360
+ self.assertTrue(res["passed_audit"])
361
+ self.assertEqual(res["trivial_assertions_count"], 0)
362
+
363
+ def test_detect_mock_leakage(self) -> None:
364
+ excessive_mock_code = (
365
+ "from unittest.mock import Mock, patch\n"
366
+ "@patch('service.api')\n"
367
+ "def test_stubbed(mock_api):\n"
368
+ " m1 = Mock()\n"
369
+ " m2 = Mock()\n"
370
+ " m3 = Mock()\n"
371
+ )
372
+ res = self.engine.detect_mock_leakage(excessive_mock_code)
373
+ self.assertGreaterEqual(res["mock_count"], 3)
374
+ self.assertTrue(res["has_excessive_mocking"])
375
+
376
+ def test_enforce_negative_paths(self) -> None:
377
+ code_with_negatives = (
378
+ "import unittest\n"
379
+ "class TestErrors(unittest.TestCase):\n"
380
+ " def test_invalid_input_error(self):\n"
381
+ " with self.assertRaises(ValueError):\n"
382
+ " int('abc')\n"
383
+ )
384
+ res = self.engine.enforce_negative_paths(code_with_negatives)
385
+ self.assertTrue(res["has_negative_tests"])
386
+ self.assertGreaterEqual(res["negative_test_count"], 1)
387
+
388
+
389
+ class TestPropertyOracleEngine(unittest.TestCase):
390
+ def setUp(self) -> None:
391
+ self.engine = PropertyOracleEngine()
392
+
393
+ def test_generate_property_matrix(self) -> None:
394
+ matrix = self.engine.generate_property_matrix(["str", "int", "float", "list"], count=30)
395
+ self.assertEqual(len(matrix), 30)
396
+ # Check boundary elements exist
397
+ self.assertTrue(any(isinstance(x, str) and x == "" for x in matrix))
398
+ self.assertTrue(any(isinstance(x, int) and x == 0 for x in matrix))
399
+ self.assertTrue(any(isinstance(x, float) for x in matrix))
400
+ self.assertTrue(any(isinstance(x, list) and len(x) == 0 for x in matrix))
401
+
402
+ def test_verify_algebraic_invariants_holds(self) -> None:
403
+ module_code = (
404
+ "import json\n"
405
+ "def encode(x):\n"
406
+ " return json.dumps(x)\n"
407
+ "def decode(s):\n"
408
+ " return json.loads(s)\n"
409
+ )
410
+ sample_inputs = [{"a": 1}, [1, 2, 3], "test string", 42, True]
411
+ res = self.engine.verify_algebraic_invariants(module_code, "encode", "decode", sample_inputs)
412
+ self.assertTrue(res["roundtrip_invariant_holds"])
413
+ self.assertEqual(res["passed"], 5)
414
+ self.assertEqual(res["failed"], 0)
415
+
416
+ def test_verify_algebraic_invariants_fails(self) -> None:
417
+ module_code = (
418
+ "def encode(x):\n"
419
+ " return x + 1\n"
420
+ "def decode(x):\n"
421
+ " return x # buggy decode, missing - 1\n"
422
+ )
423
+ sample_inputs = [10, 20, 30]
424
+ res = self.engine.verify_algebraic_invariants(module_code, "encode", "decode", sample_inputs)
425
+ self.assertFalse(res["roundtrip_invariant_holds"])
426
+ self.assertEqual(res["failed"], 3)
427
+ self.assertGreaterEqual(len(res["failure_examples"]), 1)
428
+
429
+
430
+ class TestReceiptAttestorEngine(unittest.TestCase):
431
+ def setUp(self) -> None:
432
+ self.engine = ReceiptAttestorEngine()
433
+
434
+ def test_attest_execution_and_verify(self) -> None:
435
+ cmd = [sys.executable, "-c", "print('attest_test_output')"]
436
+ receipt = self.engine.attest_execution(cmd)
437
+ self.assertEqual(receipt["exit_code"], 0)
438
+ self.assertIn("attest_test_output", receipt["stdout"])
439
+ self.assertTrue(receipt["tamper_evident"])
440
+ self.assertGreater(receipt["pid"], 0)
441
+
442
+ # Verification must pass
443
+ self.assertTrue(self.engine.verify_receipt(receipt))
444
+
445
+ def test_verify_receipt_tampered(self) -> None:
446
+ cmd = [sys.executable, "-c", "print('ok')"]
447
+ receipt = self.engine.attest_execution(cmd)
448
+ self.assertTrue(self.engine.verify_receipt(receipt))
449
+
450
+ # Tamper with stdout
451
+ tampered_receipt = dict(receipt)
452
+ tampered_receipt["stdout"] = "tampered output\n"
453
+ self.assertFalse(self.engine.verify_receipt(tampered_receipt))
454
+
455
+ # Tamper with exit code
456
+ tampered_exit = dict(receipt)
457
+ tampered_exit["exit_code"] = 1
458
+ self.assertFalse(self.engine.verify_receipt(tampered_exit))
459
+
460
+
461
+ class TestComputeOrchestratorEngine(unittest.TestCase):
462
+ def setUp(self) -> None:
463
+ self.engine = ComputeOrchestratorEngine()
464
+
465
+ def test_calculate_thinking_budget(self) -> None:
466
+ # Low complexity
467
+ low_res = self.engine.calculate_thinking_budget(complexity_score=2.0, failure_count=0)
468
+ self.assertEqual(low_res["model_tier"], "flash")
469
+ self.assertLessEqual(low_res["recommended_tokens"], 16384)
470
+
471
+ # High complexity with failures
472
+ high_res = self.engine.calculate_thinking_budget(complexity_score=9.0, failure_count=2)
473
+ self.assertEqual(high_res["model_tier"], "deepthink")
474
+ self.assertGreaterEqual(high_res["recommended_tokens"], 32768)
475
+
476
+ def test_mcts_explore(self) -> None:
477
+ spec = {"problem": "find best search algorithm", "strategies": ["binary_search", "hash_index", "b_tree"]}
478
+ res = self.engine.mcts_explore(spec, branches=3, depth=2)
479
+ self.assertGreater(res["root_visits"], 0)
480
+ self.assertIn("best_branch", res)
481
+ self.assertGreater(len(res["recommended_path"]), 0)
482
+ self.assertEqual(len(res["tree_summary"]), 3)
483
+
484
+ def test_best_of_n_consensus(self) -> None:
485
+ candidates = [
486
+ {"id": "cand_A", "score": 0.95, "test_pass_rate": 1.0, "complexity": 3.0},
487
+ {"id": "cand_B", "score": 0.80, "test_pass_rate": 0.9, "complexity": 7.0},
488
+ {"id": "cand_C", "score": 0.50, "test_pass_rate": 0.6, "complexity": 9.0},
489
+ ]
490
+ res = self.engine.best_of_n_consensus(candidates)
491
+ self.assertEqual(res["selected_candidate"]["id"], "cand_A")
492
+ self.assertGreater(res["consensus_score"], 0.0)
493
+ self.assertEqual(len(res["rankings"]), 3)
494
+
495
+
496
+ class TestCoderFleetDispatcher(unittest.TestCase):
497
+ def setUp(self) -> None:
498
+ self.dispatcher = CoderFleetDispatcher()
499
+
500
+ def test_list_actions(self) -> None:
501
+ actions = self.dispatcher.list_actions()
502
+ expected = [
503
+ "render_vector", "perceptual_diff", "extract_palette_and_boxes",
504
+ "run_diagnostics", "apply_quick_fix",
505
+ "query_ast", "rename_symbol", "verify_syntax",
506
+ "create_checkpoint", "inspect_patch", "rollback", "commit_milestone",
507
+ "run_scratch_test", "concurrency_fuzz", "profile_memory_and_cpu",
508
+ "inject_mutants", "audit_test_strength",
509
+ "audit_assertions", "detect_mock_leakage", "enforce_negative_paths",
510
+ "generate_property_matrix", "verify_algebraic_invariants",
511
+ "attest_execution", "verify_receipt",
512
+ "calculate_thinking_budget", "mcts_explore", "best_of_n_consensus",
513
+ ]
514
+ for a in expected:
515
+ self.assertIn(a, actions)
516
+
517
+ def test_dispatch_render_vector(self) -> None:
518
+ svg_code = '<svg viewBox="0 0 50 50"><circle cx="25" cy="25" r="20"/></svg>'
519
+ res = self.dispatcher.dispatch("render_vector", {"code": svg_code})
520
+ self.assertTrue(res["success"])
521
+ self.assertTrue(res["result"]["valid"])
522
+
523
+ def test_dispatch_run_diagnostics(self) -> None:
524
+ res = self.dispatcher.dispatch("run_diagnostics", {"source_code": "x = 1\n"})
525
+ self.assertTrue(res["success"])
526
+ self.assertEqual(len(res["result"]), 0)
527
+
528
+ def test_dispatch_unknown_action(self) -> None:
529
+ res = self.dispatcher.dispatch("unknown_fleet_action", {})
530
+ self.assertFalse(res["success"])
531
+ self.assertIn("Unknown fleet action", res["error"])
532
+
533
+
534
+ if __name__ == "__main__":
535
+ unittest.main()
@@ -0,0 +1,54 @@
1
+ import unittest
2
+ from pathlib import Path
3
+ import sys
4
+
5
+ BASE_DIR = Path(__file__).resolve().parent.parent
6
+ for p in [str(BASE_DIR), str(BASE_DIR / "fable_engine"), str(Path(__file__).resolve().parent)]:
7
+ if p not in sys.path:
8
+ sys.path.insert(0, p)
9
+
10
+ from server import DelegationContractCompiler
11
+
12
+
13
+ class TestDelegationContractCompiler(unittest.TestCase):
14
+ def setUp(self):
15
+ self.compiler = DelegationContractCompiler()
16
+
17
+ def test_vague_prompt_fails_all_checks(self):
18
+ prompt = "Hey please implement the payment gateway and test it."
19
+ valid, errors, parsed = self.compiler.compile_and_validate(prompt)
20
+ self.assertFalse(valid)
21
+ self.assertEqual(len(errors), 4)
22
+ self.assertTrue(any("TargetFile" in e for e in errors))
23
+ self.assertTrue(any("VerificationCommand" in e for e in errors))
24
+ self.assertTrue(any("InterfaceContract" in e for e in errors))
25
+ self.assertTrue(any("StrictConstraints" in e for e in errors))
26
+
27
+ def test_valid_delegation_contract_passes(self):
28
+ prompt = """
29
+ ### SUBAGENT DELEGATION CONTRACT
30
+ - TargetFile: src/payment/gateway.py
31
+ - InterfaceContract: def process_charge(amount_cents: int, token: str) -> PaymentResult
32
+ - StrictConstraints: Zero-alloc hot path, no network blocking, idempotent retry key
33
+ - VerificationCommand: pytest tests/test_payment.py -v
34
+ """
35
+ valid, errors, parsed = self.compiler.compile_and_validate(prompt)
36
+ self.assertTrue(valid, f"Unexpected errors: {errors}")
37
+ self.assertEqual(len(errors), 0)
38
+ self.assertEqual(parsed.get("TargetFile"), "src/payment/gateway.py")
39
+ self.assertEqual(parsed.get("VerificationCommand"), "pytest tests/test_payment.py -v")
40
+
41
+ def test_missing_verification_command_fails(self):
42
+ prompt = """
43
+ ### SUBAGENT DELEGATION CONTRACT
44
+ - TargetFile: src/utils/math.py
45
+ - InterfaceContract: def add(a: int, b: int) -> int
46
+ - StrictConstraints: Pure function, zero side-effects
47
+ """
48
+ valid, errors, parsed = self.compiler.compile_and_validate(prompt)
49
+ self.assertFalse(valid)
50
+ self.assertTrue(any("VerificationCommand" in e for e in errors))
51
+
52
+
53
+ if __name__ == "__main__":
54
+ unittest.main()