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.
- fable_compressor.py +356 -0
- fable_engine/__init__.py +1 -0
- fable_engine/actions/__init__.py +291 -0
- fable_engine/actions/cas.py +182 -0
- fable_engine/actions/deliberation.py +523 -0
- fable_engine/actions/fleet.py +807 -0
- fable_engine/actions/lifecycle.py +298 -0
- fable_engine/actions/scrapers.py +116 -0
- fable_engine/actions/system3.py +815 -0
- fable_engine/browser.py +824 -0
- fable_engine/cas.py +974 -0
- fable_engine/fable_session.json +510 -0
- fable_engine/guards.py +283 -0
- fable_engine/schema.py +714 -0
- fable_engine/scrapers/__init__.py +32 -0
- fable_engine/scrapers/arxiv.py +115 -0
- fable_engine/scrapers/base.py +386 -0
- fable_engine/scrapers/github.py +129 -0
- fable_engine/scrapers/reddit.py +154 -0
- fable_engine/scrapers/web.py +120 -0
- fable_engine/scrapers/x.py +125 -0
- fable_engine/scrapers/youtube.py +132 -0
- fable_engine/server.py +414 -0
- fable_engine/session.py +1819 -0
- fable_engine/test_server.py +1362 -0
- fable_engine/updater.py +541 -0
- fable_engine-1.3.1.dist-info/LICENSE +22 -0
- fable_engine-1.3.1.dist-info/METADATA +173 -0
- fable_engine-1.3.1.dist-info/RECORD +104 -0
- fable_engine-1.3.1.dist-info/WHEEL +5 -0
- fable_engine-1.3.1.dist-info/entry_points.txt +5 -0
- fable_engine-1.3.1.dist-info/top_level.txt +6 -0
- fable_mode/__init__.py +3 -0
- fable_mode/__main__.py +4 -0
- fable_mode/adapters.py +1014 -0
- fable_mode/installer.py +553 -0
- fable_mode/launcher.py +437 -0
- fable_mode/manifest.py +142 -0
- fable_mode/resources.json +114 -0
- fable_mode/safety.py +103 -0
- fable_mode_entry.py +10 -0
- fable_v2/__init__.py +146 -0
- fable_v2/adapters.py +151 -0
- fable_v2/coder_fleet/__init__.py +100 -0
- fable_v2/coder_fleet/ast_tools.py +158 -0
- fable_v2/coder_fleet/compute.py +199 -0
- fable_v2/coder_fleet/design_engine.py +1316 -0
- fable_v2/coder_fleet/diagnostics.py +293 -0
- fable_v2/coder_fleet/fleet_dispatcher.py +214 -0
- fable_v2/coder_fleet/mock_auditor.py +306 -0
- fable_v2/coder_fleet/mutation.py +216 -0
- fable_v2/coder_fleet/property_oracle.py +260 -0
- fable_v2/coder_fleet/receipt_attestor.py +122 -0
- fable_v2/coder_fleet/red_team_swarm.py +908 -0
- fable_v2/coder_fleet/test_harness.py +198 -0
- fable_v2/coder_fleet/vector_engine.py +1287 -0
- fable_v2/coder_fleet/visual.py +357 -0
- fable_v2/coder_fleet/workspace.py +153 -0
- fable_v2/cortical/__init__.py +20 -0
- fable_v2/cortical/plasticity_engine.py +992 -0
- fable_v2/execution_broker.py +811 -0
- fable_v2/proof_engine.py +1141 -0
- fable_v2/protocol.py +485 -0
- fable_v2/runtime.py +1010 -0
- fable_v2/system3/__init__.py +204 -0
- fable_v2/system3/causal.py +558 -0
- fable_v2/system3/dialectical.py +577 -0
- fable_v2/system3/evolution.py +503 -0
- fable_v2/system3/executive.py +338 -0
- fable_v2/system3/free_energy.py +479 -0
- fable_v2/system3/hyperbolic.py +555 -0
- fable_v2/system3/induction.py +336 -0
- fable_v2/system3/kripke.py +548 -0
- fable_v2/system3/oracle.py +745 -0
- fable_v2/verifiers.py +72 -0
- tests/__init__.py +1 -0
- tests/test_anti_loop_circuit_breaker.py +64 -0
- tests/test_auto_updater.py +407 -0
- tests/test_coder_fleet.py +535 -0
- tests/test_delegation_compiler.py +54 -0
- tests/test_descriptor_boundaries.py +126 -0
- tests/test_design_engine.py +603 -0
- tests/test_epistemic_evidence_validator.py +66 -0
- tests/test_execution_broker.py +233 -0
- tests/test_fable_v2.py +406 -0
- tests/test_fleet_transitions.py +116 -0
- tests/test_fsm_redteam_evolution.py +406 -0
- tests/test_goal_rubric_and_pipeline.py +367 -0
- tests/test_hebbian_plasticity.py +585 -0
- tests/test_packaging_runtime.py +194 -0
- tests/test_proof_engine.py +259 -0
- tests/test_red_team_swarm.py +645 -0
- tests/test_redteam_remediation.py +169 -0
- tests/test_registration_transaction.py +375 -0
- tests/test_requested_regressions.py +467 -0
- tests/test_scrapers.py +370 -0
- tests/test_server_actions.py +93 -0
- tests/test_server_frontier_actions.py +269 -0
- tests/test_server_protocol.py +88 -0
- tests/test_stealth_browser.py +970 -0
- tests/test_system3.py +381 -0
- tests/test_system3_deep_integration.py +385 -0
- tests/test_system3_frontier.py +436 -0
- 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)
|