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
tests/test_scrapers.py ADDED
@@ -0,0 +1,370 @@
1
+ """
2
+ Unit tests for Fable Engine Zero-Cost Research Scrapers, SSRF protection,
3
+ alternate numeric IP canonicalization, redirect validation, IP pinning,
4
+ clamped retry delays, content boundaries, error handling, and MCP Action Handlers.
5
+ """
6
+
7
+ import json
8
+ import unittest
9
+ import urllib.error
10
+ from unittest.mock import MagicMock, patch
11
+
12
+ from fable_engine.actions import handle_fable_session
13
+ from fable_engine.scrapers import (
14
+ ArxivScraper,
15
+ GitHubScraper,
16
+ RedditScraper,
17
+ ResearchResult,
18
+ WebScraper,
19
+ XScraper,
20
+ YouTubeScraper,
21
+ fetch_url,
22
+ scrape_arxiv,
23
+ scrape_github,
24
+ scrape_reddit,
25
+ scrape_web,
26
+ scrape_x,
27
+ scrape_youtube,
28
+ )
29
+ from fable_engine.scrapers.base import PinnedHTTPSConnection, parse_canonical_ip, validate_safe_url
30
+ from fable_engine.session import get_or_load_session
31
+
32
+
33
+ class TestSSRFProtectionAndRateLimiting(unittest.TestCase):
34
+
35
+ def test_alternate_numeric_ip_canonicalization(self):
36
+ self.assertEqual(parse_canonical_ip("127.0.0.1"), "127.0.0.1")
37
+ self.assertEqual(parse_canonical_ip("2130706433"), "127.0.0.1")
38
+ self.assertEqual(parse_canonical_ip("127.1"), "127.0.0.1")
39
+ self.assertEqual(parse_canonical_ip("0x7f000001"), "127.0.0.1")
40
+ self.assertEqual(parse_canonical_ip("0177.0.0.1"), "127.0.0.1")
41
+ self.assertIsNone(parse_canonical_ip("arxiv.org"))
42
+
43
+ def test_ssrf_validation_blocks_unsafe_hosts_and_alternate_ips(self):
44
+ unsafe_urls = [
45
+ "http://localhost:8080/admin",
46
+ "http://127.0.0.1/secret",
47
+ "http://127.0.0.255/",
48
+ "http://2130706433/secret",
49
+ "http://127.1/admin",
50
+ "http://0x7f000001/status",
51
+ "http://0177.0.0.1/keys",
52
+ "http://10.0.0.1/internal",
53
+ "http://172.16.0.1/status",
54
+ "http://192.168.1.1/router",
55
+ "http://169.254.169.254/latest/meta-data/",
56
+ "file:///etc/passwd",
57
+ "gopher://localhost:70",
58
+ "ftp://ftp.local/data",
59
+ ]
60
+ for url in unsafe_urls:
61
+ safe, reason, resolved_ip = validate_safe_url(url)
62
+ self.assertFalse(safe, f"Expected '{url}' to be blocked by SSRF validation, but it passed.")
63
+
64
+ def test_ssrf_validation_allows_public_urls(self):
65
+ safe_urls = [
66
+ "https://arxiv.org/abs/2401.12345",
67
+ "https://api.github.com/repos/REX-codebase/fable-mode",
68
+ "https://www.reddit.com/r/Python/hot.json",
69
+ ]
70
+ for url in safe_urls:
71
+ safe, reason, resolved_ip = validate_safe_url(url)
72
+ self.assertTrue(safe, f"Expected public URL '{url}' to pass SSRF validation, failed: {reason}")
73
+ self.assertTrue(resolved_ip is not None)
74
+
75
+ def test_fetch_url_raises_value_error_on_ssrf_target(self):
76
+ with self.assertRaises(ValueError) as ctx:
77
+ fetch_url("http://127.0.0.1:8000/keys")
78
+ self.assertIn("SSRF validation failed", str(ctx.exception))
79
+
80
+ @patch("fable_engine.scrapers.base.validate_safe_url")
81
+ def test_ssrf_redirect_to_private_ip_is_blocked(self, mock_validate):
82
+ mock_validate.side_effect = [
83
+ (True, "ok", "93.184.216.34"), # example.com (public)
84
+ (False, "SSRF blocked: target IP '127.0.0.1' is private, loopback, or reserved.", None)
85
+ ]
86
+
87
+ with patch("fable_engine.scrapers.base.PinnedHTTPSConnection") as mock_conn_cls:
88
+ mock_conn = MagicMock()
89
+ mock_resp_redirect = MagicMock()
90
+ mock_resp_redirect.status = 302
91
+ mock_resp_redirect.getheader.side_effect = lambda h: "http://127.0.0.1/secret" if h == "Location" else None
92
+ mock_conn.getresponse.return_value = mock_resp_redirect
93
+ mock_conn_cls.return_value = mock_conn
94
+
95
+ with self.assertRaises(ValueError) as ctx:
96
+ fetch_url("https://example.com/redirect-me")
97
+ self.assertIn("SSRF validation failed", str(ctx.exception))
98
+ self.assertIn("127.0.0.1", str(ctx.exception))
99
+
100
+
101
+ class TestResearchResultAndContentBoundaries(unittest.TestCase):
102
+
103
+ def test_untrusted_content_boundary_formatting(self):
104
+ res = ResearchResult(
105
+ ok=True,
106
+ source_type="web",
107
+ canonical_url="https://example.com",
108
+ title="Sample Page",
109
+ content="Sample webpage text."
110
+ )
111
+ self.assertEqual(res.metadata.get("trust_level"), "untrusted_external_content")
112
+ md = res.to_markdown()
113
+ self.assertIn("[BEGIN UNTRUSTED EXTERNAL RESEARCH CONTENT]", md)
114
+ self.assertIn("Sample webpage text.", md)
115
+ self.assertIn("[END UNTRUSTED EXTERNAL RESEARCH CONTENT]", md)
116
+
117
+
118
+ class TestResearchScrapers(unittest.TestCase):
119
+
120
+ @patch("fable_engine.scrapers.web.fetch_url")
121
+ def test_scrape_web_url_success(self, mock_fetch):
122
+ mock_fetch.return_value = """
123
+ <html>
124
+ <head><title>Test Page</title></head>
125
+ <body>
126
+ <h1>Hello World</h1>
127
+ <p>This is a test paragraph with a <a href="https://example.com/link">link</a>.</p>
128
+ </body>
129
+ </html>
130
+ """
131
+ res = scrape_web("https://example.com/page")
132
+ self.assertTrue(res.ok)
133
+ self.assertEqual(res.source_type, "web")
134
+ self.assertEqual(res.canonical_url, "https://example.com/page")
135
+ self.assertIn("Test Page", res.title)
136
+ md = res.to_markdown()
137
+ self.assertIn("Hello World", md)
138
+ self.assertIn("[link](https://example.com/link)", md)
139
+
140
+ @patch("fable_engine.scrapers.web.fetch_url")
141
+ def test_scrape_web_http_error_handling(self, mock_fetch):
142
+ mock_fetch.side_effect = urllib.error.HTTPError("https://example.com/forbidden", 403, "Forbidden", {}, None)
143
+ res = scrape_web("https://example.com/forbidden")
144
+ self.assertFalse(res.ok)
145
+ self.assertIn("403", res.error)
146
+ md = res.to_markdown()
147
+ self.assertIn("Research Retrieval Failed", md)
148
+
149
+ @patch("fable_engine.scrapers.youtube.fetch_url")
150
+ def test_scrape_youtube_video_success(self, mock_fetch):
151
+ def side_effect(url, *args, **kwargs):
152
+ if "watch?v=" in url:
153
+ return """
154
+ <html>
155
+ <meta property="og:title" content="Test AI Video">
156
+ <meta property="og:description" content="A great video about AI.">
157
+ <link itemprop="name" content="TechChannel">
158
+ "captionTracks":[{"baseUrl":"https://youtube.com/api/timedtext"}]
159
+ </html>
160
+ """
161
+ elif "timedtext" in url:
162
+ return """<?xml version="1.0" encoding="utf-8" ?>
163
+ <transcript>
164
+ <text start="0" dur="2">Welcome to the video.</text>
165
+ <text start="2" dur="3">Today we discuss AI agents.</text>
166
+ </transcript>
167
+ """
168
+ return ""
169
+
170
+ mock_fetch.side_effect = side_effect
171
+ res = scrape_youtube("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
172
+ self.assertTrue(res.ok)
173
+ self.assertEqual(res.source_type, "youtube")
174
+ self.assertEqual(res.canonical_url, "https://www.youtube.com/watch?v=dQw4w9WgXcQ")
175
+ self.assertIn("Test AI Video", res.title)
176
+ self.assertIn("Welcome to the video. Today we discuss AI agents.", res.content)
177
+
178
+ @patch("fable_engine.scrapers.youtube.fetch_url")
179
+ def test_scrape_youtube_missing_transcript_fallback(self, mock_fetch):
180
+ mock_fetch.return_value = """
181
+ <html>
182
+ <meta property="og:title" content="No Captions Video">
183
+ <link itemprop="name" content="TechChannel">
184
+ </html>
185
+ """
186
+ res = scrape_youtube("dQw4w9WgXcQ")
187
+ self.assertTrue(res.ok)
188
+ self.assertIn("Best-effort automated transcript track status", res.content)
189
+
190
+ @patch("fable_engine.scrapers.reddit.fetch_url")
191
+ def test_scrape_reddit_thread_success(self, mock_fetch):
192
+ mock_response = [
193
+ {
194
+ "data": {
195
+ "children": [
196
+ {
197
+ "data": {
198
+ "title": "Fable Engine Released",
199
+ "subreddit": "Python",
200
+ "author": "rex_dev",
201
+ "score": 150,
202
+ "selftext": "Check out the new cognitive runtime.",
203
+ "permalink": "/r/Python/comments/123/fable/"
204
+ }
205
+ }
206
+ ]
207
+ }
208
+ },
209
+ {
210
+ "data": {
211
+ "children": [
212
+ {
213
+ "data": {
214
+ "author": "user1",
215
+ "body": "Looks amazing!",
216
+ "score": 25
217
+ }
218
+ }
219
+ ]
220
+ }
221
+ }
222
+ ]
223
+ mock_fetch.return_value = json.dumps(mock_response)
224
+ res = scrape_reddit("https://www.reddit.com/r/Python/comments/123/fable/")
225
+ self.assertTrue(res.ok)
226
+ self.assertEqual(res.source_type, "reddit")
227
+ self.assertIn("Fable Engine Released", res.title)
228
+ self.assertIn("Check out the new cognitive runtime.", res.content)
229
+ self.assertIn("u/user1 (25 points)", res.content)
230
+
231
+ @patch("fable_engine.scrapers.reddit.fetch_url")
232
+ def test_scrape_reddit_empty_structure_handling(self, mock_fetch):
233
+ mock_fetch.return_value = json.dumps([])
234
+ res = scrape_reddit("r/empty_sub")
235
+ self.assertFalse(res.ok)
236
+ self.assertTrue(res.error is not None)
237
+
238
+ @patch("fable_engine.scrapers.x.fetch_url")
239
+ def test_scrape_x_tweet_success(self, mock_fetch):
240
+ mock_tweet = {
241
+ "text": "Fable Mode is now 100% free with research scrapers!",
242
+ "user": {
243
+ "name": "Fable Engine",
244
+ "screen_name": "fable_mode"
245
+ },
246
+ "created_at": "Mon Mar 03 12:00:00 +0000 2025",
247
+ "favorite_count": 42,
248
+ "retweet_count": 10
249
+ }
250
+ mock_fetch.return_value = json.dumps(mock_tweet)
251
+ res = scrape_x("https://x.com/fable_mode/status/1234567890")
252
+ self.assertTrue(res.ok)
253
+ self.assertEqual(res.source_type, "x")
254
+ self.assertIn("Fable Engine", res.title)
255
+ self.assertIn("100% free", res.content)
256
+
257
+ @patch("fable_engine.scrapers.github.fetch_url")
258
+ def test_scrape_github_repo_success(self, mock_fetch):
259
+ def side_effect(url, *args, **kwargs):
260
+ if "api.github.com/repos" in url:
261
+ return json.dumps({
262
+ "full_name": "REX-codebase/fable-mode",
263
+ "description": "Control plane for AI agents",
264
+ "stargazers_count": 100,
265
+ "forks_count": 12,
266
+ "language": "Python",
267
+ "default_branch": "main",
268
+ "html_url": "https://github.com/REX-codebase/fable-mode"
269
+ })
270
+ elif "raw.githubusercontent.com" in url:
271
+ return "# Fable Mode\nStructured Deliberation for Agents."
272
+ return ""
273
+
274
+ mock_fetch.side_effect = side_effect
275
+ res = scrape_github("REX-codebase/fable-mode")
276
+ self.assertTrue(res.ok)
277
+ self.assertEqual(res.source_type, "github")
278
+ self.assertIn("REX-codebase/fable-mode", res.title)
279
+ self.assertIn("Control plane for AI agents", res.content)
280
+
281
+ @patch("fable_engine.scrapers.arxiv.fetch_url")
282
+ def test_scrape_arxiv_paper_success(self, mock_fetch):
283
+ mock_xml = """<?xml version="1.0" encoding="UTF-8"?>
284
+ <feed xmlns="http://www.w3.org/2005/Atom">
285
+ <entry>
286
+ <id>http://arxiv.org/abs/2401.12345v1</id>
287
+ <published>2024-01-20T10:00:00Z</published>
288
+ <title>Cognitive Deliberation in AI Agents</title>
289
+ <summary>We propose Fable Mode for structured deliberation.</summary>
290
+ <author><name>Alice Smith</name></author>
291
+ <author><name>Bob Jones</name></author>
292
+ </entry>
293
+ </feed>
294
+ """
295
+ mock_fetch.return_value = mock_xml
296
+ res = scrape_arxiv("2401.12345")
297
+ self.assertTrue(res.ok)
298
+ self.assertEqual(res.source_type, "arxiv")
299
+ self.assertIn("Cognitive Deliberation in AI Agents", res.content)
300
+
301
+
302
+ class TestScraperActionsAndEpistemicLog(unittest.TestCase):
303
+
304
+ @patch("fable_engine.actions.scrapers.scrape_arxiv")
305
+ def test_mcp_action_dispatch(self, mock_scrape):
306
+ mock_scrape.return_value = ResearchResult(
307
+ ok=True,
308
+ source_type="arxiv",
309
+ canonical_url="https://arxiv.org/abs/2401.12345",
310
+ title="AI Deliberation",
311
+ content="Paper abstract."
312
+ )
313
+ resp = handle_fable_session({
314
+ "action": "scrape_arxiv",
315
+ "target": "2401.12345"
316
+ })
317
+ self.assertIn("AI Deliberation", resp)
318
+ mock_scrape.assert_called_once_with("2401.12345")
319
+
320
+ @patch("fable_engine.actions.scrapers.scrape_github")
321
+ def test_auto_log_epistemic_hypothesis_success_only(self, mock_scrape):
322
+ session_name = "test_scraper_session"
323
+
324
+ handle_fable_session({
325
+ "action": "create_session",
326
+ "session_name": session_name,
327
+ "objective": "Test scraping auto-log as HYPOTHESIS",
328
+ "time_budget_minutes": 2.0
329
+ })
330
+
331
+ # 1. Test failed result (ok=False) -> MUST NOT log hypothesis
332
+ mock_scrape.return_value = ResearchResult(
333
+ ok=False,
334
+ source_type="github",
335
+ canonical_url="https://github.com/invalid/repo",
336
+ error="404 Not Found"
337
+ )
338
+ handle_fable_session({
339
+ "action": "scrape_github",
340
+ "session_name": session_name,
341
+ "target": "invalid/repo",
342
+ "auto_log_epistemic": True
343
+ })
344
+
345
+ session = get_or_load_session(session_name)
346
+ self.assertEqual(len(session.epistemic_ledger), 0)
347
+
348
+ # 2. Test successful result (ok=True) -> MUST log hypothesis
349
+ mock_scrape.return_value = ResearchResult(
350
+ ok=True,
351
+ source_type="github",
352
+ canonical_url="https://github.com/REX-codebase/fable-mode",
353
+ title="REX-codebase/fable-mode",
354
+ content="# GitHub Repo\nName: fable-mode"
355
+ )
356
+ handle_fable_session({
357
+ "action": "scrape_github",
358
+ "session_name": session_name,
359
+ "target": "REX-codebase/fable-mode",
360
+ "auto_log_epistemic": True
361
+ })
362
+
363
+ session = get_or_load_session(session_name)
364
+ hypothesis_items = [item for item in session.epistemic_ledger if item["tag"] == "HYPOTHESIS"]
365
+ self.assertEqual(len(hypothesis_items), 1)
366
+ self.assertIn("REX-codebase/fable-mode", hypothesis_items[0]["claim"])
367
+
368
+
369
+ if __name__ == "__main__":
370
+ unittest.main()
@@ -0,0 +1,93 @@
1
+ import unittest
2
+ import json
3
+ import time
4
+ from pathlib import Path
5
+ import sys
6
+
7
+ BASE_DIR = Path(__file__).resolve().parent.parent
8
+ for p in [str(BASE_DIR), str(BASE_DIR / "fable_engine"), str(Path(__file__).resolve().parent)]:
9
+ if p not in sys.path:
10
+ sys.path.insert(0, p)
11
+
12
+ from server import handle_fable_session, ACTIVE_SESSIONS, SESSIONS_DIR, FableSession
13
+
14
+
15
+ class TestServerActions(unittest.TestCase):
16
+ def setUp(self):
17
+ self.session_name = f"test_session_{int(time.time() * 1000)}"
18
+
19
+ def tearDown(self):
20
+ if self.session_name in ACTIVE_SESSIONS:
21
+ del ACTIVE_SESSIONS[self.session_name]
22
+ session_file = SESSIONS_DIR / f"{self.session_name}.json"
23
+ if session_file.exists():
24
+ try:
25
+ session_file.unlink()
26
+ except Exception:
27
+ pass
28
+
29
+ def test_session_lifecycle_with_silent_deliberation(self):
30
+ # 1. Create Session
31
+ res = handle_fable_session({
32
+ "action": "create_session",
33
+ "session_name": self.session_name,
34
+ "objective": "Verify frontier uplift protocol",
35
+ "time_budget_minutes": 30.0
36
+ })
37
+ self.assertIn("Fable Cognitive Session Initialized", res)
38
+ self.assertIn("SILENT-DELIBERATION ACTIVE", res)
39
+
40
+ session = ACTIVE_SESSIONS[self.session_name]
41
+ tel = session.get_telemetry()
42
+ self.assertTrue(tel["silent_deliberation_active"])
43
+ self.assertTrue(tel["execution_locked"])
44
+
45
+ # 2. Log Proven Item with valid command evidence
46
+ res = handle_fable_session({
47
+ "action": "log_epistemic_item",
48
+ "session_name": self.session_name,
49
+ "tag": "PROVEN",
50
+ "claim": "Python 3 installed",
51
+ "evidence": "python --version stdout: Python 3.12"
52
+ })
53
+ self.assertIn("Epistemic Item Logged", res)
54
+ self.assertIn("SILENT-DELIBERATION ACTIVE", res)
55
+
56
+ # 3. Log Proven Item with invalid file evidence -> Expect error
57
+ res = handle_fable_session({
58
+ "action": "log_epistemic_item",
59
+ "session_name": self.session_name,
60
+ "tag": "PROVEN",
61
+ "claim": "Bogus claim",
62
+ "evidence": "non_existent_file_xyz_123.py:L10"
63
+ })
64
+ self.assertIn("Error:", res)
65
+ self.assertIn("Epistemic Evidence Validation Failed", res)
66
+
67
+ # 4. Compile Delegation Contract
68
+ valid_contract = """
69
+ ### SUBAGENT DELEGATION CONTRACT
70
+ - TargetFile: src/core/engine.py
71
+ - InterfaceContract: def process_event(event: Event) -> bool
72
+ - StrictConstraints: Lock-free, zero heap allocation in loop
73
+ - VerificationCommand: pytest tests/test_engine.py
74
+ """
75
+ res = handle_fable_session({
76
+ "action": "compile_delegation_contract",
77
+ "session_name": self.session_name,
78
+ "subagent_prompt": valid_contract
79
+ })
80
+ self.assertIn("Subagent Delegation Contract Compiled Successfully", res)
81
+ self.assertIn("READY_FOR_SUBAGENT_DISPATCH", res)
82
+
83
+ # 5. Compile Invalid Contract
84
+ res = handle_fable_session({
85
+ "action": "compile_delegation_contract",
86
+ "session_name": self.session_name,
87
+ "subagent_prompt": "Please write the code"
88
+ })
89
+ self.assertIn("Subagent Delegation Contract Compilation Failed", res)
90
+
91
+
92
+ if __name__ == "__main__":
93
+ unittest.main()