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
fable_engine/cas.py ADDED
@@ -0,0 +1,974 @@
1
+ """
2
+ Content-Addressed Storage (CAS) and Token Compression Subsystem.
3
+ Implements FableCASStore, AdaptiveChunkAccumulator, FableGrammar333,
4
+ CASSliceViewer, and FableCompress for 100% lossless token compaction.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import collections
10
+ import hashlib
11
+ import io
12
+ import json
13
+ import os
14
+ import stat
15
+ import struct
16
+ import sys
17
+ import tempfile
18
+ import threading
19
+ import time
20
+ from pathlib import Path
21
+ from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
22
+
23
+ MAX_CAS_OBJECT_BYTES = 16 * 1024 * 1024
24
+ MAX_SLICE_RESPONSE_BYTES = 1_000_000
25
+ MAX_RPC_RESPONSE_BYTES = 2 * 1024 * 1024
26
+
27
+ # Base directories
28
+ BASE_DIR = Path(__file__).resolve().parent
29
+ _DATA_ENV = os.environ.get("FABLE_DATA_DIR")
30
+ if _DATA_ENV:
31
+ DATA_DIR = Path(_DATA_ENV).expanduser().absolute()
32
+ elif os.name == "nt" and os.environ.get("LOCALAPPDATA"):
33
+ DATA_DIR = Path(os.environ["LOCALAPPDATA"]) / "FableMode" / "data"
34
+ else:
35
+ DATA_DIR = Path.home() / ".local" / "share" / "fable-engine" / "data"
36
+
37
+ FABLE_CAS_DIR = Path(os.environ.get("FABLE_CAS_DIR", DATA_DIR / "cas"))
38
+
39
+
40
+ def _assert_private_path(path: Path) -> None:
41
+ srv = sys.modules.get("fable_engine.server")
42
+ if srv is not None:
43
+ handler = getattr(srv, "_assert_private_path", None)
44
+ if handler is not None and handler is not _assert_private_path:
45
+ return handler(path)
46
+ cur = path
47
+ parts: list[Path] = []
48
+ while True:
49
+ parts.append(cur)
50
+ if cur.parent == cur:
51
+ break
52
+ cur = cur.parent
53
+ for part in reversed(parts):
54
+ try:
55
+ st = part.lstat()
56
+ except FileNotFoundError:
57
+ continue
58
+ attrs = int(getattr(st, "st_file_attributes", 0))
59
+ trusted_macos_alias = (
60
+ sys.platform == "darwin" and str(part) in {"/var", "/tmp"}
61
+ and str(part.resolve()) in {"/private/var", "/private/tmp"}
62
+ )
63
+ if ((attrs & 0x400 or stat.S_ISLNK(st.st_mode)) and not trusted_macos_alias) or stat.S_ISSOCK(st.st_mode) or stat.S_ISFIFO(st.st_mode) or stat.S_ISCHR(st.st_mode) or stat.S_ISBLK(st.st_mode):
64
+ raise RuntimeError("state path contains a symlink, reparse point, or special file")
65
+
66
+
67
+ class FableCASError(Exception):
68
+ """Base exception for Fable CAS errors."""
69
+ pass
70
+
71
+
72
+ class IntegrityError(FableCASError):
73
+ """Raised when SHA-256 integrity verification fails."""
74
+ pass
75
+
76
+
77
+ class CASNotFoundError(FableCASError):
78
+ """Raised when a requested CAS object does not exist."""
79
+ pass
80
+
81
+
82
+ def _open_directory_nofollow(path: Path, *, create: bool = False) -> int:
83
+ """Open a directory chain without following links, retaining its identity."""
84
+ if os.name != "posix" or not hasattr(os, "O_NOFOLLOW"):
85
+ raise FableCASError("descriptor-relative state access is unavailable")
86
+ absolute = Path(path).absolute()
87
+ directory_flags = (getattr(os, "O_PATH", os.O_RDONLY)
88
+ | getattr(os, "O_DIRECTORY", 0) | os.O_NOFOLLOW)
89
+ fd = os.open("/", directory_flags)
90
+ try:
91
+ for component in absolute.parts[1:]:
92
+ component_flags = directory_flags
93
+ if (sys.platform == "darwin" and component in {"var", "tmp"}
94
+ and str(Path("/", component).resolve()) in {"/private/var", "/private/tmp"}):
95
+ component_flags = directory_flags & ~os.O_NOFOLLOW
96
+ try:
97
+ child = os.open(component, component_flags, dir_fd=fd)
98
+ except FileNotFoundError:
99
+ if not create:
100
+ raise FableCASError(f"missing state directory: {absolute}")
101
+ os.mkdir(component, 0o700, dir_fd=fd)
102
+ child = os.open(component, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | os.O_NOFOLLOW, dir_fd=fd)
103
+ os.close(fd)
104
+ fd = child
105
+ return fd
106
+ except Exception:
107
+ os.close(fd)
108
+ raise
109
+
110
+
111
+ def _safe_cas_node(path: Path, *, allow_missing: bool = True) -> None:
112
+ """Reject links/reparse points/special files before any CAS file access."""
113
+ srv = sys.modules.get("fable_engine.server")
114
+ if srv is not None:
115
+ handler = getattr(srv, "_safe_cas_node", None)
116
+ if handler is not None and handler is not _safe_cas_node:
117
+ return handler(path, allow_missing=allow_missing)
118
+ cur = Path(path)
119
+ parts: list[Path] = []
120
+ while True:
121
+ parts.append(cur)
122
+ if cur.parent == cur:
123
+ break
124
+ cur = cur.parent
125
+ for part in reversed(parts):
126
+ try:
127
+ st = part.lstat()
128
+ except FileNotFoundError:
129
+ if allow_missing:
130
+ continue
131
+ raise FableCASError(f"missing CAS path: {part}")
132
+ attrs = int(getattr(st, "st_file_attributes", 0))
133
+ trusted_macos_alias = (
134
+ sys.platform == "darwin" and str(part) in {"/var", "/tmp"}
135
+ and str(part.resolve()) in {"/private/var", "/private/tmp"}
136
+ )
137
+ if (((attrs & 0x400 or stat.S_ISLNK(st.st_mode)) and not trusted_macos_alias)
138
+ or stat.S_ISSOCK(st.st_mode) or stat.S_ISFIFO(st.st_mode)
139
+ or stat.S_ISCHR(st.st_mode) or stat.S_ISBLK(st.st_mode)):
140
+ raise FableCASError(f"unsafe CAS path: {part}")
141
+ if part == Path(path) and stat.S_ISREG(st.st_mode):
142
+ if st.st_nlink != 1 or (os.name != "nt" and stat.S_IMODE(st.st_mode) & 0o077):
143
+ raise FableCASError(f"CAS object is not private: {part}")
144
+
145
+
146
+ class ThreadSafeLRUCache:
147
+ """Thread-safe Least-Recently-Used (LRU) memory cache."""
148
+
149
+ def __init__(self, capacity: int = 256):
150
+ if capacity <= 0:
151
+ raise ValueError("LRU capacity must be greater than zero.")
152
+ self.capacity = capacity
153
+ self._cache: collections.OrderedDict[str, Union[str, bytes]] = collections.OrderedDict()
154
+ self._lock = threading.Lock()
155
+
156
+ def get(self, key: str) -> Optional[Union[str, bytes]]:
157
+ with self._lock:
158
+ if key in self._cache:
159
+ self._cache.move_to_end(key)
160
+ return self._cache[key]
161
+ return None
162
+
163
+ def put(self, key: str, value: Union[str, bytes]) -> None:
164
+ with self._lock:
165
+ if key in self._cache:
166
+ self._cache.move_to_end(key)
167
+ self._cache[key] = value
168
+ if len(self._cache) > self.capacity:
169
+ self._cache.popitem(last=False)
170
+
171
+ def contains(self, key: str) -> bool:
172
+ with self._lock:
173
+ return key in self._cache
174
+
175
+ def clear(self) -> None:
176
+ with self._lock:
177
+ self._cache.clear()
178
+
179
+ def __len__(self) -> int:
180
+ with self._lock:
181
+ return len(self._cache)
182
+
183
+
184
+ class FableCASStore:
185
+ """
186
+ Content-Addressed Storage (CAS) with lock-free atomic tmp-replace writes,
187
+ SHA-256 integrity validation, two-level shard hierarchy, and LRU memory caching.
188
+ """
189
+
190
+ URI_PREFIX = "cas://"
191
+
192
+ def __init__(
193
+ self,
194
+ root_dir: Optional[Union[str, Path]] = None,
195
+ cache_capacity: int = 256,
196
+ auto_verify: bool = True,
197
+ ):
198
+ self.root_dir = Path(root_dir).expanduser().absolute() if root_dir is not None else DATA_DIR / "cas"
199
+ _assert_private_path(self.root_dir)
200
+ self.root_dir.mkdir(parents=True, exist_ok=True)
201
+ _assert_private_path(self.root_dir)
202
+ if self.root_dir.is_symlink() or not self.root_dir.is_dir():
203
+ raise FableCASError("CAS root must be a real directory")
204
+ os.chmod(self.root_dir, 0o700)
205
+ self.objects_dir = self.root_dir / "objects"
206
+ self.tmp_dir = self.root_dir / ".tmp"
207
+ self.objects_dir.mkdir(parents=True, exist_ok=True)
208
+ self.tmp_dir.mkdir(parents=True, exist_ok=True)
209
+ for directory in (self.objects_dir, self.tmp_dir):
210
+ if directory.is_symlink() or not directory.is_dir():
211
+ raise FableCASError("CAS directory must be a real directory")
212
+ os.chmod(directory, 0o700)
213
+
214
+ self.cache = ThreadSafeLRUCache(capacity=cache_capacity)
215
+ self.auto_verify = auto_verify
216
+ self._write_lock = threading.Lock()
217
+
218
+ @classmethod
219
+ def compute_sha256(cls, data: Union[str, bytes]) -> Tuple[str, bytes]:
220
+ """Compute SHA-256 hex digest and raw bytes from str or bytes."""
221
+ if isinstance(data, str):
222
+ raw = data.encode("utf-8")
223
+ elif isinstance(data, (bytes, bytearray)):
224
+ raw = bytes(data)
225
+ else:
226
+ raise TypeError(f"Expected str or bytes, got {type(data).__name__}")
227
+
228
+ hasher = hashlib.sha256()
229
+ hasher.update(raw)
230
+ return hasher.hexdigest(), raw
231
+
232
+ @classmethod
233
+ def normalize_ref(cls, ref_or_hash: str) -> str:
234
+ """Strip 'cas://' prefix and validate 64-char hex format."""
235
+ cleaned = ref_or_hash.strip()
236
+ if cleaned.startswith(cls.URI_PREFIX):
237
+ cleaned = cleaned[len(cls.URI_PREFIX):]
238
+ if len(cleaned) != 64 or not all(c in "0123456789abcdefABCDEF" for c in cleaned):
239
+ raise ValueError(f"Invalid SHA-256 hash reference: {ref_or_hash!r}")
240
+ return cleaned.lower()
241
+
242
+ @classmethod
243
+ def to_uri(cls, content_hash: str) -> str:
244
+ """Format 64-char hex hash as standard cas:// URI."""
245
+ return f"{cls.URI_PREFIX}{content_hash.lower()}"
246
+
247
+ def _get_object_path(self, content_hash: str) -> Path:
248
+ """Return two-level sharded path: objects/ab/cdef1234..."""
249
+ shard = content_hash[:2]
250
+ rest = content_hash[2:]
251
+ return self.objects_dir / shard / rest
252
+
253
+ def _open_object(self, content_hash: str, flags: int, *, create_parent: bool = False) -> tuple[int, int, str]:
254
+ """Open a CAS object relative to a no-follow shard directory."""
255
+ object_path = self._get_object_path(content_hash)
256
+ shard_fd = _open_directory_nofollow(object_path.parent, create=create_parent)
257
+ try:
258
+ object_fd = os.open(object_path.name, flags | getattr(os, "O_NOFOLLOW", 0), dir_fd=shard_fd)
259
+ except Exception:
260
+ os.close(shard_fd)
261
+ raise
262
+ return shard_fd, object_fd, object_path.name
263
+
264
+ def exists(self, ref_or_hash: str) -> bool:
265
+ """Check if content hash exists in memory cache or on disk."""
266
+ content_hash = self.normalize_ref(ref_or_hash)
267
+ path = self._get_object_path(content_hash)
268
+ try:
269
+ _safe_cas_node(path)
270
+ except FableCASError:
271
+ return False
272
+ return path.is_file()
273
+
274
+ def _put_posix(self, content_hash: str, raw_bytes: bytes) -> str:
275
+ """Publish an object through pinned directory descriptors."""
276
+ dest_path = self._get_object_path(content_hash)
277
+ shard_fd = _open_directory_nofollow(dest_path.parent, create=True)
278
+ tmp_fd_dir = _open_directory_nofollow(self.tmp_dir, create=False)
279
+ temp_name = f"cas_tmp_{content_hash[:8]}_{os.getpid()}_{os.urandom(8).hex()}.tmp"
280
+ object_fd = None
281
+ data_fd = None
282
+ try:
283
+ try:
284
+ object_fd = os.open(dest_path.name, os.O_RDONLY | os.O_NOFOLLOW, dir_fd=shard_fd)
285
+ existing = os.read(object_fd, MAX_CAS_OBJECT_BYTES + 1)
286
+ if len(existing) > MAX_CAS_OBJECT_BYTES or hashlib.sha256(existing).hexdigest() != content_hash:
287
+ raise IntegrityError("existing CAS object is corrupt")
288
+ self.cache.put(content_hash, existing)
289
+ return self.to_uri(content_hash)
290
+ except FileNotFoundError:
291
+ pass
292
+ finally:
293
+ if object_fd is not None:
294
+ os.close(object_fd)
295
+ object_fd = None
296
+ data_fd = os.open(temp_name, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600, dir_fd=tmp_fd_dir)
297
+ view = memoryview(raw_bytes)
298
+ while view:
299
+ written = os.write(data_fd, view)
300
+ view = view[written:]
301
+ os.fsync(data_fd)
302
+ os.close(data_fd)
303
+ data_fd = None
304
+ os.replace(temp_name, dest_path.name, src_dir_fd=tmp_fd_dir, dst_dir_fd=shard_fd)
305
+ self.cache.put(content_hash, raw_bytes)
306
+ return self.to_uri(content_hash)
307
+ finally:
308
+ if object_fd is not None:
309
+ os.close(object_fd)
310
+ if data_fd is not None:
311
+ os.close(data_fd)
312
+ try:
313
+ os.unlink(temp_name, dir_fd=tmp_fd_dir)
314
+ except OSError:
315
+ pass
316
+ os.close(tmp_fd_dir)
317
+ os.close(shard_fd)
318
+
319
+ def put(self, content: Union[str, bytes]) -> str:
320
+ """
321
+ Store content in CAS using lock-free atomic tmp-replace write.
322
+ Returns the standard URI: cas://<sha256_hex>.
323
+ """
324
+ content_hash, raw_bytes = self.compute_sha256(content)
325
+ dest_path = self._get_object_path(content_hash)
326
+ _safe_cas_node(dest_path)
327
+
328
+ if len(raw_bytes) > MAX_CAS_OBJECT_BYTES:
329
+ raise FableCASError("CAS object exceeds maximum size")
330
+ if os.name == "posix" and hasattr(os, "O_NOFOLLOW"):
331
+ return self._put_posix(content_hash, raw_bytes)
332
+ _safe_cas_node(dest_path)
333
+ if dest_path.is_file():
334
+ try:
335
+ with dest_path.open("rb") as existing:
336
+ existing_bytes = existing.read(MAX_CAS_OBJECT_BYTES + 1)
337
+ except OSError as exc:
338
+ raise FableCASError("could not verify existing CAS object") from exc
339
+ if len(existing_bytes) > MAX_CAS_OBJECT_BYTES or hashlib.sha256(existing_bytes).hexdigest() != content_hash:
340
+ raise IntegrityError("existing CAS object is corrupt")
341
+ self.cache.put(content_hash, existing_bytes)
342
+ return self.to_uri(content_hash)
343
+
344
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
345
+ _safe_cas_node(dest_path.parent)
346
+
347
+ tmp_fd, tmp_file_path = tempfile.mkstemp(
348
+ prefix=f"cas_tmp_{content_hash[:8]}_",
349
+ suffix=".tmp",
350
+ dir=str(self.tmp_dir)
351
+ )
352
+
353
+ try:
354
+ with os.fdopen(tmp_fd, "wb") as f:
355
+ f.write(raw_bytes)
356
+ f.flush()
357
+ os.fsync(f.fileno())
358
+
359
+ os.replace(tmp_file_path, dest_path)
360
+ except Exception:
361
+ if os.path.exists(tmp_file_path):
362
+ try:
363
+ os.remove(tmp_file_path)
364
+ except OSError:
365
+ pass
366
+ raise
367
+
368
+ self.cache.put(content_hash, raw_bytes)
369
+ return self.to_uri(content_hash)
370
+
371
+ def get_bytes(self, ref_or_hash: str, verify: Optional[bool] = None) -> bytes:
372
+ """Retrieve bytes, verifying SHA-256 unless explicitly opted out."""
373
+ content_hash = self.normalize_ref(ref_or_hash)
374
+ should_verify = self.auto_verify if verify is None else verify
375
+
376
+ dest_path = self._get_object_path(content_hash)
377
+ _safe_cas_node(dest_path)
378
+ if not dest_path.is_file():
379
+ raise CASNotFoundError(f"CAS object not found: {ref_or_hash}")
380
+ cached = self.cache.get(content_hash)
381
+ if cached is not None and not isinstance(cached, (bytes, str)):
382
+ raise IntegrityError("CAS cache contains an unsupported value type")
383
+ if cached is not None and not should_verify:
384
+ data = cached if isinstance(cached, bytes) else cached.encode("utf-8")
385
+ else:
386
+ with open(dest_path, "rb") as f:
387
+ data = f.read(MAX_CAS_OBJECT_BYTES + 1)
388
+ if cached is not None:
389
+ cached_bytes = cached if isinstance(cached, bytes) else cached.encode("utf-8")
390
+ if cached_bytes != data:
391
+ raise IntegrityError("CAS cache does not match the on-disk object")
392
+ if len(data) > MAX_CAS_OBJECT_BYTES:
393
+ raise FableCASError("CAS object exceeds maximum size")
394
+ if should_verify:
395
+ actual_hash = hashlib.sha256(data).hexdigest()
396
+ if actual_hash != content_hash:
397
+ raise IntegrityError(
398
+ f"Integrity check failed for {content_hash}! Actual SHA-256: {actual_hash}"
399
+ )
400
+ self.cache.put(content_hash, data)
401
+ return data
402
+
403
+ def get_text(self, ref_or_hash: str, verify: Optional[bool] = None) -> str:
404
+ """Retrieve UTF-8 text."""
405
+ content_hash = self.normalize_ref(ref_or_hash)
406
+ should_verify = self.auto_verify if verify is None else verify
407
+ cached = self.cache.get(content_hash)
408
+ if cached is not None and isinstance(cached, str) and not should_verify:
409
+ return cached
410
+
411
+ data = self.get_bytes(content_hash, verify=should_verify)
412
+ text = data.decode("utf-8", errors="strict")
413
+ self.cache.put(content_hash, text)
414
+ return text
415
+
416
+ def verify_integrity(self, ref_or_hash: str) -> bool:
417
+ """Explicitly re-compute and check the SHA-256 hash of a CAS object."""
418
+ try:
419
+ content_hash = self.normalize_ref(ref_or_hash)
420
+ dest_path = self._get_object_path(content_hash)
421
+ _safe_cas_node(dest_path)
422
+ if not dest_path.is_file():
423
+ return False
424
+ with open(dest_path, "rb") as f:
425
+ data = f.read(MAX_CAS_OBJECT_BYTES + 1)
426
+ if len(data) > MAX_CAS_OBJECT_BYTES:
427
+ return False
428
+ actual_hash = hashlib.sha256(data).hexdigest()
429
+ return actual_hash == content_hash
430
+ except Exception:
431
+ return False
432
+
433
+ def get_file_path(self, ref_or_hash: str) -> Path:
434
+ """Return a path only after a bounded content-address verification."""
435
+ content_hash = self.normalize_ref(ref_or_hash)
436
+ path = self._get_object_path(content_hash)
437
+ _safe_cas_node(path)
438
+ if not path.is_file():
439
+ raise CASNotFoundError(f"CAS object not found on disk: {ref_or_hash}")
440
+ self.get_bytes(content_hash, verify=True)
441
+ return path
442
+
443
+
444
+ class CompositeFrame:
445
+ """Represents a batched composite frame of micro-payloads."""
446
+
447
+ def __init__(self, frame_id: str, items: List[Dict[str, Any]]):
448
+ self.frame_id = frame_id
449
+ self.items = items
450
+ self.created_at = time.time()
451
+
452
+ def serialize_json(self) -> str:
453
+ """Serialize frame manifest and payloads to canonical JSON."""
454
+ return json.dumps(
455
+ {
456
+ "frame_id": self.frame_id,
457
+ "count": len(self.items),
458
+ "created_at": self.created_at,
459
+ "items": self.items,
460
+ },
461
+ ensure_ascii=False,
462
+ separators=(",", ":"),
463
+ )
464
+
465
+ @classmethod
466
+ def deserialize_json(cls, data: str) -> CompositeFrame:
467
+ """Deserialize frame from canonical JSON."""
468
+ parsed = json.loads(data)
469
+ frame = cls(frame_id=parsed["frame_id"], items=parsed["items"])
470
+ frame.created_at = parsed.get("created_at", time.time())
471
+ return frame
472
+
473
+
474
+ class AdaptiveChunkAccumulator:
475
+ """
476
+ Coalesces sub-1000 character micro-payloads into composite frames of 1KB+
477
+ to prevent CAS pointer bloat while preserving 100% lossless extraction.
478
+ """
479
+
480
+ def __init__(
481
+ self,
482
+ cas_store: FableCASStore,
483
+ min_frame_size: int = 1024,
484
+ max_frame_size: int = 65536,
485
+ ):
486
+ self.cas_store = cas_store
487
+ self.min_frame_size = min_frame_size
488
+ self.max_frame_size = max_frame_size
489
+ self._buffer: List[Dict[str, Any]] = []
490
+ self._buffered_chars: int = 0
491
+ self._lock = threading.Lock()
492
+ self._frame_counter: int = 0
493
+
494
+ # Telemetry
495
+ self.total_payloads_ingested: int = 0
496
+ self.total_frames_flushed: int = 0
497
+ self.total_raw_chars: int = 0
498
+ self.total_cas_bytes_written: int = 0
499
+
500
+ def add(
501
+ self,
502
+ payload: str,
503
+ metadata: Optional[Dict[str, Any]] = None,
504
+ force_flush: bool = False,
505
+ ) -> List[str]:
506
+ """Add a micro-payload to accumulator."""
507
+ if not isinstance(payload, str):
508
+ raise TypeError(f"Payload must be str, got {type(payload).__name__}")
509
+
510
+ flushed_uris: List[str] = []
511
+ with self._lock:
512
+ self.total_payloads_ingested += 1
513
+ payload_len = len(payload)
514
+ self.total_raw_chars += payload_len
515
+
516
+ entry = {
517
+ "idx": len(self._buffer),
518
+ "payload": payload,
519
+ "meta": metadata or {},
520
+ "ts": time.time(),
521
+ }
522
+ self._buffer.append(entry)
523
+ self._buffered_chars += payload_len
524
+
525
+ if force_flush or self._buffered_chars >= self.min_frame_size:
526
+ uri = self._flush_internal_locked()
527
+ if uri:
528
+ flushed_uris.append(uri)
529
+
530
+ while self._buffered_chars >= self.max_frame_size:
531
+ uri = self._flush_internal_locked()
532
+ if uri:
533
+ flushed_uris.append(uri)
534
+ else:
535
+ break
536
+
537
+ return flushed_uris
538
+
539
+ def flush(self) -> List[str]:
540
+ """Explicitly flush all remaining buffered micro-payloads into a composite frame."""
541
+ with self._lock:
542
+ if not self._buffer:
543
+ return []
544
+ uri = self._flush_internal_locked()
545
+ return [uri] if uri else []
546
+
547
+ def _flush_internal_locked(self) -> Optional[str]:
548
+ """Internal flush implementation assuming caller holds self._lock."""
549
+ if not self._buffer:
550
+ return None
551
+
552
+ self._frame_counter += 1
553
+ frame_id = f"frame_{int(time.time())}_{self._frame_counter}_{os.urandom(4).hex()}"
554
+ frame = CompositeFrame(frame_id=frame_id, items=list(self._buffer))
555
+ serialized_frame = frame.serialize_json()
556
+
557
+ uri = self.cas_store.put(serialized_frame)
558
+ self.total_frames_flushed += 1
559
+ self.total_cas_bytes_written += len(serialized_frame.encode("utf-8"))
560
+
561
+ self._buffer = []
562
+ self._buffered_chars = 0
563
+ return uri
564
+
565
+ def extract_item(self, frame_uri: str, item_index: int) -> Tuple[str, Dict[str, Any]]:
566
+ """Extract a specific micro-payload by index from a flushed composite frame."""
567
+ frame_json = self.cas_store.get_text(frame_uri)
568
+ frame = CompositeFrame.deserialize_json(frame_json)
569
+ if 0 <= item_index < len(frame.items):
570
+ item = frame.items[item_index]
571
+ return item["payload"], item["meta"]
572
+ raise IndexError(f"Item index {item_index} out of bounds for frame with {len(frame.items)} items.")
573
+
574
+ def get_stats(self) -> Dict[str, Any]:
575
+ """Return runtime telemetry for compression efficiency profiling."""
576
+ with self._lock:
577
+ buffered_items = len(self._buffer)
578
+ buffered_chars = self._buffered_chars
579
+
580
+ cas_bytes = self.total_cas_bytes_written
581
+ raw_chars = self.total_raw_chars
582
+ reduction_pct = round((1.0 - (cas_bytes / max(1, raw_chars))) * 100.0, 2) if raw_chars > 0 else 0.0
583
+
584
+ return {
585
+ "total_payloads_ingested": self.total_payloads_ingested,
586
+ "total_frames_flushed": self.total_frames_flushed,
587
+ "total_raw_chars": raw_chars,
588
+ "total_cas_bytes_written": cas_bytes,
589
+ "currently_buffered_items": buffered_items,
590
+ "currently_buffered_chars": buffered_chars,
591
+ "current_buffered_items": buffered_items,
592
+ "current_buffered_chars": buffered_chars,
593
+ "storage_reduction_pct": reduction_pct,
594
+ }
595
+
596
+
597
+
598
+ class FableGrammar333:
599
+ """
600
+ High-Entropy Micro-Bytecode Serializer for Agent Actions & Reasoning Nodes.
601
+ Translates JSON/action dicts into high-density binary wire-format.
602
+ Guarantees 100% lossless bit-exact roundtrip deserialization.
603
+ """
604
+
605
+ MAGIC_HEADER = b"\x33\x33\x33\x01" # Grammar333 Protocol v1
606
+
607
+ OP_RECORD_ACTION = 0x10
608
+ OP_EPISTEMIC_ITEM = 0x20
609
+ OP_INVARIANT = 0x30
610
+ OP_REFINEMENT = 0x40
611
+ OP_GENERIC_JSON = 0xFF
612
+
613
+ TYPE_NULL = 0x00
614
+ TYPE_BOOL_TRUE = 0x01
615
+ TYPE_BOOL_FALSE = 0x02
616
+ TYPE_INT = 0x03
617
+ TYPE_FLOAT = 0x04
618
+ TYPE_STR = 0x05
619
+ TYPE_BYTES = 0x06
620
+ TYPE_ARRAY = 0x07
621
+ TYPE_MAP = 0x08
622
+
623
+ @classmethod
624
+ def write_varint(cls, buffer: io.BytesIO, value: int) -> None:
625
+ """Write variable-length zigzag encoded integer."""
626
+ zigzag = (value << 1) ^ (value >> 63) if value < 0 else (value << 1)
627
+ while True:
628
+ byte = zigzag & 0x7F
629
+ zigzag >>= 7
630
+ if zigzag != 0:
631
+ buffer.write(bytes([byte | 0x80]))
632
+ else:
633
+ buffer.write(bytes([byte]))
634
+ break
635
+
636
+ @classmethod
637
+ def read_varint(cls, stream: io.BytesIO) -> int:
638
+ """Read variable-length zigzag encoded integer."""
639
+ result = 0
640
+ shift = 0
641
+ while True:
642
+ byte_bytes = stream.read(1)
643
+ if not byte_bytes:
644
+ raise EOFError("Unexpected EOF while reading varint")
645
+ byte = byte_bytes[0]
646
+ result |= (byte & 0x7F) << shift
647
+ shift += 7
648
+ if not (byte & 0x80):
649
+ break
650
+ value = (result >> 1) ^ -(result & 1)
651
+ return value
652
+
653
+ @classmethod
654
+ def write_string(cls, buffer: io.BytesIO, s: str) -> None:
655
+ """Write UTF-8 string prefixed by varint byte length."""
656
+ encoded = s.encode("utf-8")
657
+ cls.write_varint(buffer, len(encoded))
658
+ buffer.write(encoded)
659
+
660
+ @classmethod
661
+ def read_string(cls, stream: io.BytesIO) -> str:
662
+ """Read UTF-8 string prefixed by varint byte length."""
663
+ length = cls.read_varint(stream)
664
+ if length < 0:
665
+ raise ValueError(f"Negative string length: {length}")
666
+ data = stream.read(length)
667
+ if len(data) != length:
668
+ raise EOFError(f"Expected {length} string bytes, got {len(data)}")
669
+ return data.decode("utf-8")
670
+
671
+ @classmethod
672
+ def write_value(cls, buffer: io.BytesIO, val: Any) -> None:
673
+ """Write typed primitive value into binary stream."""
674
+ if val is None:
675
+ buffer.write(bytes([cls.TYPE_NULL]))
676
+ elif isinstance(val, bool):
677
+ buffer.write(bytes([cls.TYPE_BOOL_TRUE if val else cls.TYPE_BOOL_FALSE]))
678
+ elif isinstance(val, int):
679
+ buffer.write(bytes([cls.TYPE_INT]))
680
+ cls.write_varint(buffer, val)
681
+ elif isinstance(val, float):
682
+ buffer.write(bytes([cls.TYPE_FLOAT]))
683
+ buffer.write(struct.pack(">d", val))
684
+ elif isinstance(val, str):
685
+ buffer.write(bytes([cls.TYPE_STR]))
686
+ cls.write_string(buffer, val)
687
+ elif isinstance(val, (bytes, bytearray)):
688
+ buffer.write(bytes([cls.TYPE_BYTES]))
689
+ cls.write_varint(buffer, len(val))
690
+ buffer.write(bytes(val))
691
+ elif isinstance(val, list):
692
+ buffer.write(bytes([cls.TYPE_ARRAY]))
693
+ cls.write_varint(buffer, len(val))
694
+ for item in val:
695
+ cls.write_value(buffer, item)
696
+ elif isinstance(val, dict):
697
+ buffer.write(bytes([cls.TYPE_MAP]))
698
+ cls.write_varint(buffer, len(val))
699
+ for k, v in val.items():
700
+ cls.write_string(buffer, str(k))
701
+ cls.write_value(buffer, v)
702
+ else:
703
+ s_val = json.dumps(val)
704
+ buffer.write(bytes([cls.TYPE_STR]))
705
+ cls.write_string(buffer, s_val)
706
+
707
+ @classmethod
708
+ def read_value(cls, stream: io.BytesIO) -> Any:
709
+ """Read typed primitive value from binary stream."""
710
+ type_byte = stream.read(1)
711
+ if not type_byte:
712
+ raise EOFError("Unexpected EOF while reading type byte")
713
+ t = type_byte[0]
714
+ if t == cls.TYPE_NULL:
715
+ return None
716
+ elif t == cls.TYPE_BOOL_TRUE:
717
+ return True
718
+ elif t == cls.TYPE_BOOL_FALSE:
719
+ return False
720
+ elif t == cls.TYPE_INT:
721
+ return cls.read_varint(stream)
722
+ elif t == cls.TYPE_FLOAT:
723
+ data = stream.read(8)
724
+ if len(data) != 8:
725
+ raise EOFError("Failed to read 8 float bytes")
726
+ return struct.unpack(">d", data)[0]
727
+ elif t == cls.TYPE_STR:
728
+ return cls.read_string(stream)
729
+ elif t == cls.TYPE_BYTES:
730
+ length = cls.read_varint(stream)
731
+ data = stream.read(length)
732
+ if len(data) != length:
733
+ raise EOFError("Failed to read raw byte payload")
734
+ return data
735
+ elif t == cls.TYPE_ARRAY:
736
+ count = cls.read_varint(stream)
737
+ return [cls.read_value(stream) for _ in range(count)]
738
+ elif t == cls.TYPE_MAP:
739
+ count = cls.read_varint(stream)
740
+ res = {}
741
+ for _ in range(count):
742
+ k = cls.read_string(stream)
743
+ v = cls.read_value(stream)
744
+ res[k] = v
745
+ return res
746
+ else:
747
+ raise ValueError(f"Unknown Grammar333 type byte: 0x{t:02X}")
748
+
749
+ @classmethod
750
+ def serialize(cls, action_payload: Dict[str, Any]) -> bytes:
751
+ """Serialize an action dictionary to Grammar333 micro-bytecode."""
752
+ buffer = io.BytesIO()
753
+ buffer.write(cls.MAGIC_HEADER)
754
+ action_type = action_payload.get("action", "")
755
+
756
+ if action_type in ("log_epistemic_item", "epistemic_item"):
757
+ buffer.write(bytes([cls.OP_EPISTEMIC_ITEM]))
758
+ cls.write_string(buffer, str(action_payload.get("tag", "HYPOTHESIS")))
759
+ cls.write_string(buffer, str(action_payload.get("claim", "")))
760
+ cls.write_string(buffer, str(action_payload.get("evidence", "")))
761
+ cls.write_value(buffer, action_payload.get("metadata", {}))
762
+
763
+ elif action_type in ("record_invariant", "invariant"):
764
+ buffer.write(bytes([cls.OP_INVARIANT]))
765
+ cls.write_string(buffer, str(action_payload.get("invariant_name", "")))
766
+ cls.write_string(buffer, str(action_payload.get("formal_statement", "")))
767
+ cls.write_string(buffer, str(action_payload.get("proof_or_rationale", "")))
768
+ cls.write_string(buffer, str(action_payload.get("domain", "global")))
769
+
770
+ elif action_type in ("log_refinement_cycle", "refinement_cycle"):
771
+ buffer.write(bytes([cls.OP_REFINEMENT]))
772
+ cls.write_string(buffer, str(action_payload.get("refinement_type", "")))
773
+ cls.write_string(buffer, str(action_payload.get("focus_area", "")))
774
+ cls.write_string(buffer, str(action_payload.get("critique_or_bottleneck", "")))
775
+ cls.write_string(buffer, str(action_payload.get("architectural_refinement", "")))
776
+ cls.write_string(buffer, str(action_payload.get("terminal_probe_results", "")))
777
+ cls.write_string(buffer, str(action_payload.get("artifact_path", "")))
778
+
779
+ else:
780
+ buffer.write(bytes([cls.OP_GENERIC_JSON]))
781
+ canonical_json = json.dumps(action_payload, ensure_ascii=False, separators=(",", ":"))
782
+ cls.write_string(buffer, canonical_json)
783
+
784
+ return buffer.getvalue()
785
+
786
+ @classmethod
787
+ def deserialize(cls, data: bytes) -> Dict[str, Any]:
788
+ """Deserialize Grammar333 micro-bytecode back into canonical dictionary."""
789
+ stream = io.BytesIO(data)
790
+ magic = stream.read(4)
791
+ if magic != cls.MAGIC_HEADER:
792
+ raise ValueError("Invalid Grammar333 magic header")
793
+
794
+ opcode_bytes = stream.read(1)
795
+ if not opcode_bytes:
796
+ raise EOFError("Empty Grammar333 stream")
797
+ opcode = opcode_bytes[0]
798
+
799
+ if opcode == cls.OP_EPISTEMIC_ITEM:
800
+ return {
801
+ "action": "log_epistemic_item",
802
+ "tag": cls.read_string(stream),
803
+ "claim": cls.read_string(stream),
804
+ "evidence": cls.read_string(stream),
805
+ "metadata": cls.read_value(stream),
806
+ }
807
+
808
+ elif opcode == cls.OP_INVARIANT:
809
+ return {
810
+ "action": "record_invariant",
811
+ "invariant_name": cls.read_string(stream),
812
+ "formal_statement": cls.read_string(stream),
813
+ "proof_or_rationale": cls.read_string(stream),
814
+ "domain": cls.read_string(stream),
815
+ }
816
+
817
+ elif opcode == cls.OP_REFINEMENT:
818
+ return {
819
+ "action": "log_refinement_cycle",
820
+ "refinement_type": cls.read_string(stream),
821
+ "focus_area": cls.read_string(stream),
822
+ "critique_or_bottleneck": cls.read_string(stream),
823
+ "architectural_refinement": cls.read_string(stream),
824
+ "terminal_probe_results": cls.read_string(stream),
825
+ "artifact_path": cls.read_string(stream),
826
+ }
827
+
828
+ elif opcode == cls.OP_GENERIC_JSON:
829
+ return json.loads(cls.read_string(stream))
830
+
831
+ else:
832
+ raise ValueError(f"Unknown Grammar333 opcode: 0x{opcode:02X}")
833
+
834
+
835
+ class CASSliceViewer:
836
+ """
837
+ Zero-copy streaming windowed line slice extractor.
838
+ Extracts precise line ranges [start_line, end_line] (1-indexed inclusive)
839
+ directly from CAS-stored documents without loading unbounded files into memory.
840
+ """
841
+
842
+ def __init__(self, cas_store: FableCASStore):
843
+ self.cas_store = cas_store
844
+
845
+ def view_slice(
846
+ self,
847
+ ref_or_hash: str,
848
+ start_line: int,
849
+ end_line: int,
850
+ include_line_numbers: bool = False,
851
+ ) -> str:
852
+ """Extract lines from start_line to end_line (1-indexed, inclusive)."""
853
+ data = self.cas_store.get_bytes(ref_or_hash, verify=True)
854
+ if start_line < 1:
855
+ start_line = 1
856
+ if end_line < start_line:
857
+ return ""
858
+ if end_line - start_line > 100000:
859
+ raise ValueError("slice request is too large")
860
+
861
+ output_lines: List[str] = []
862
+ output_bytes = 0
863
+ current_line_num = 0
864
+
865
+ with io.TextIOWrapper(io.BytesIO(data), encoding="utf-8", errors="strict") as f:
866
+ for line in f:
867
+ current_line_num += 1
868
+ if current_line_num > end_line:
869
+ break
870
+ if current_line_num >= start_line:
871
+ content = line.rstrip("\r\n")
872
+ rendered = f"{current_line_num:6d} | {content}" if include_line_numbers else content
873
+ output_bytes += len(rendered.encode("utf-8")) + 1
874
+ if output_bytes > MAX_SLICE_RESPONSE_BYTES:
875
+ raise ValueError("slice response exceeds maximum size")
876
+ output_lines.append(rendered)
877
+
878
+ return "\n".join(output_lines)
879
+
880
+ def iter_slice(
881
+ self,
882
+ ref_or_hash: str,
883
+ start_line: int,
884
+ end_line: int,
885
+ ) -> Iterator[str]:
886
+ """Verify the object before returning a bounded streaming iterator."""
887
+ data = self.cas_store.get_bytes(ref_or_hash, verify=True)
888
+ if start_line < 1:
889
+ start_line = 1
890
+
891
+ def _lines() -> Iterator[str]:
892
+ current_line_num = 0
893
+ output_bytes = 0
894
+ with io.TextIOWrapper(io.BytesIO(data), encoding="utf-8", errors="strict") as f:
895
+ for line in f:
896
+ current_line_num += 1
897
+ if current_line_num > end_line:
898
+ break
899
+ if current_line_num >= start_line:
900
+ rendered = line.rstrip("\r\n")
901
+ output_bytes += len(rendered.encode("utf-8")) + 1
902
+ if output_bytes > MAX_SLICE_RESPONSE_BYTES:
903
+ raise ValueError("slice response exceeds maximum size")
904
+ yield rendered
905
+ return _lines()
906
+
907
+ def get_line_count(self, ref_or_hash: str) -> int:
908
+ """Count total lines in a CAS object using fast chunked buffer scanning."""
909
+ data = self.cas_store.get_bytes(ref_or_hash, verify=True)
910
+ count = 0
911
+ total_bytes = 0
912
+ buffer_size = 65536
913
+ with io.BytesIO(data) as f:
914
+ while True:
915
+ buf = f.read(buffer_size)
916
+ if not buf:
917
+ break
918
+ total_bytes += len(buf)
919
+ if total_bytes > MAX_CAS_OBJECT_BYTES:
920
+ raise FableCASError("CAS object exceeds maximum size")
921
+ count += buf.count(b"\n")
922
+ return count
923
+
924
+
925
+ class FableCompress:
926
+ """
927
+ Unified Fable-Mode Token Compression Engine.
928
+ Orchestrates CASStore, AdaptiveChunkAccumulator, FableGrammar333, and CASSliceViewer
929
+ to achieve extreme token compaction with 100% bit-exact lossless recovery.
930
+ """
931
+
932
+ def __init__(self, root_dir: Optional[Union[str, Path]] = None):
933
+ self.cas_store = FableCASStore(root_dir=root_dir)
934
+ self.accumulator = AdaptiveChunkAccumulator(self.cas_store)
935
+ self.grammar = FableGrammar333()
936
+ self.slice_viewer = CASSliceViewer(self.cas_store)
937
+
938
+ @staticmethod
939
+ def estimate_token_count(text: str) -> int:
940
+ """Token estimator approximating standard BPE tokenizers (~4.0 characters per token)."""
941
+ if not text:
942
+ return 0
943
+ return max(1, int(round(len(text) / 4.0)))
944
+
945
+ def compress_payload_to_cas(self, content: str, label: str = "output") -> Dict[str, Any]:
946
+ """Compress large content string into CAS reference pointer with metadata."""
947
+ cas_uri = self.cas_store.put(content)
948
+ line_count = self.slice_viewer.get_line_count(cas_uri)
949
+
950
+ compressed_node = {
951
+ "type": "cas_ref",
952
+ "cas_ref": cas_uri,
953
+ "lines": line_count,
954
+ }
955
+ return compressed_node
956
+
957
+ def decompress_cas_payload(self, compressed_node: Dict[str, Any]) -> str:
958
+ """Losslessly retrieve original content from compressed node."""
959
+ if compressed_node.get("type") != "cas_ref" or "cas_ref" not in compressed_node:
960
+ raise ValueError("Invalid compressed CAS node")
961
+ return self.cas_store.get_text(compressed_node["cas_ref"], verify=True)
962
+
963
+ def calculate_token_ratio(self, raw_text: str, compressed_repr: str) -> float:
964
+ """
965
+ Calculate effective tokens per raw character:
966
+ Ratio = tokens(compressed_repr) / characters(raw_text)
967
+ """
968
+ if not raw_text:
969
+ return 0.0
970
+ compressed_tokens = self.estimate_token_count(compressed_repr)
971
+ return compressed_tokens / float(len(raw_text))
972
+
973
+
974
+ CAS_ENGINE = FableCompress(root_dir=FABLE_CAS_DIR)