algo-cli-runtime 0.14.0__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.
- algo_cli/__init__.py +3 -0
- algo_cli/__main__.py +7 -0
- algo_cli/_internal/__init__.py +12 -0
- algo_cli/_internal/policy_chain.py +259 -0
- algo_cli/action_registry.py +1047 -0
- algo_cli/agent_blocks.py +550 -0
- algo_cli/agent_pipeline.py +1457 -0
- algo_cli/agent_threads.py +308 -0
- algo_cli/animations.py +316 -0
- algo_cli/cache_admission.py +209 -0
- algo_cli/capability_mask.py +66 -0
- algo_cli/chat_protocol.py +116 -0
- algo_cli/chatgpt_auth.py +510 -0
- algo_cli/chatgpt_client.py +657 -0
- algo_cli/code_rag.py +479 -0
- algo_cli/config.py +651 -0
- algo_cli/context_budget.py +679 -0
- algo_cli/credential_helpers.py +315 -0
- algo_cli/deliberation.py +29 -0
- algo_cli/display.py +1470 -0
- algo_cli/evals/__init__.py +21 -0
- algo_cli/evals/algorithm_effectiveness.py +560 -0
- algo_cli/evals/competitive_harness_rating.py +702 -0
- algo_cli/evals/cot_quality.py +220 -0
- algo_cli/evals/harness_retrieval_benchmark.py +401 -0
- algo_cli/evals/performance_regression.py +136 -0
- algo_cli/evals/scorecard_grading.py +308 -0
- algo_cli/evals/session_distribution.py +84 -0
- algo_cli/execution_guardrails.py +806 -0
- algo_cli/extensions_manifest.py +84 -0
- algo_cli/git_evidence.py +227 -0
- algo_cli/google_workspace.py +407 -0
- algo_cli/google_workspace_auth.py +523 -0
- algo_cli/harness.py +2587 -0
- algo_cli/identity.py +557 -0
- algo_cli/index_compute_lab.py +228 -0
- algo_cli/inference_harness.py +70 -0
- algo_cli/intelligence/__init__.py +1103 -0
- algo_cli/intelligence/acrobat_config.py +307 -0
- algo_cli/intelligence/acrobat_manifests.py +338 -0
- algo_cli/intelligence/acrobat_models.py +195 -0
- algo_cli/intelligence/acrobat_pipeline.py +295 -0
- algo_cli/intelligence/acrobat_runtime.py +302 -0
- algo_cli/intelligence/acrobat_security.py +261 -0
- algo_cli/intelligence/acrobat_workflows.py +226 -0
- algo_cli/intelligence/actionability.py +165 -0
- algo_cli/intelligence/adversarial_audit.py +136 -0
- algo_cli/intelligence/agent_arena.py +92 -0
- algo_cli/intelligence/agent_benchmark.py +236 -0
- algo_cli/intelligence/agent_runtime.py +171 -0
- algo_cli/intelligence/agents_as_tools.py +70 -0
- algo_cli/intelligence/artifact_binding.py +80 -0
- algo_cli/intelligence/autonomous_engineer.py +1976 -0
- algo_cli/intelligence/backpressure.py +99 -0
- algo_cli/intelligence/bloom_filter.py +186 -0
- algo_cli/intelligence/bonferroni.py +66 -0
- algo_cli/intelligence/boundary_compaction.py +98 -0
- algo_cli/intelligence/catalog_verifier.py +172 -0
- algo_cli/intelligence/cavecrew.py +118 -0
- algo_cli/intelligence/changelog.py +176 -0
- algo_cli/intelligence/checkpoint_resume.py +92 -0
- algo_cli/intelligence/circuit_breaker.py +88 -0
- algo_cli/intelligence/clarification_gate.py +101 -0
- algo_cli/intelligence/code_graph.py +180 -0
- algo_cli/intelligence/coderank.py +97 -0
- algo_cli/intelligence/consistent_hash.py +150 -0
- algo_cli/intelligence/consortium_synthesis.py +139 -0
- algo_cli/intelligence/construction/__init__.py +241 -0
- algo_cli/intelligence/construction/common.py +273 -0
- algo_cli/intelligence/construction/documents.py +496 -0
- algo_cli/intelligence/construction/labor_units.py +1395 -0
- algo_cli/intelligence/construction/payments.py +470 -0
- algo_cli/intelligence/construction/risk.py +784 -0
- algo_cli/intelligence/content_extractor.py +132 -0
- algo_cli/intelligence/context_adaptive.py +102 -0
- algo_cli/intelligence/context_ops.py +95 -0
- algo_cli/intelligence/count_min.py +145 -0
- algo_cli/intelligence/cow_state.py +103 -0
- algo_cli/intelligence/critic_loop.py +119 -0
- algo_cli/intelligence/cross_source.py +113 -0
- algo_cli/intelligence/daemon_mode.py +99 -0
- algo_cli/intelligence/dag_orchestration.py +151 -0
- algo_cli/intelligence/deep_research.py +155 -0
- algo_cli/intelligence/degenerate_detector.py +78 -0
- algo_cli/intelligence/delta_report.py +92 -0
- algo_cli/intelligence/discovery_event_log.py +92 -0
- algo_cli/intelligence/document_ingest.py +298 -0
- algo_cli/intelligence/dual_layer_validate.py +151 -0
- algo_cli/intelligence/echo_fidelity.py +73 -0
- algo_cli/intelligence/ema_tuning.py +104 -0
- algo_cli/intelligence/event_log.py +92 -0
- algo_cli/intelligence/evidence_graph.py +114 -0
- algo_cli/intelligence/extension_host.py +162 -0
- algo_cli/intelligence/extension_manifest.py +115 -0
- algo_cli/intelligence/falsification_suite.py +178 -0
- algo_cli/intelligence/finance/__init__.py +169 -0
- algo_cli/intelligence/finance/anomalies.py +135 -0
- algo_cli/intelligence/finance/ap_ar.py +351 -0
- algo_cli/intelligence/finance/cash.py +162 -0
- algo_cli/intelligence/finance/close.py +332 -0
- algo_cli/intelligence/finance/common.py +244 -0
- algo_cli/intelligence/finance/construction.py +135 -0
- algo_cli/intelligence/finance/controls.py +172 -0
- algo_cli/intelligence/finance/evidence.py +119 -0
- algo_cli/intelligence/finance/exceptions.py +157 -0
- algo_cli/intelligence/finance/reconciliations.py +254 -0
- algo_cli/intelligence/finance/revenue.py +109 -0
- algo_cli/intelligence/finance/tax.py +74 -0
- algo_cli/intelligence/finance/workpapers.py +111 -0
- algo_cli/intelligence/finding_record.py +120 -0
- algo_cli/intelligence/flow_dag.py +267 -0
- algo_cli/intelligence/gatherer.py +223 -0
- algo_cli/intelligence/golden_master.py +98 -0
- algo_cli/intelligence/graph_rag.py +195 -0
- algo_cli/intelligence/group_chat.py +143 -0
- algo_cli/intelligence/hash_dedup.py +145 -0
- algo_cli/intelligence/hyperloglog.py +128 -0
- algo_cli/intelligence/incremental_index.py +316 -0
- algo_cli/intelligence/index_store.py +16 -0
- algo_cli/intelligence/iteration_plan.py +133 -0
- algo_cli/intelligence/kernel_plugins.py +167 -0
- algo_cli/intelligence/lesson_catalog.py +135 -0
- algo_cli/intelligence/llm_fallback.py +169 -0
- algo_cli/intelligence/log2_histogram.py +267 -0
- algo_cli/intelligence/lsp_integration.py +147 -0
- algo_cli/intelligence/memory_evolution.py +117 -0
- algo_cli/intelligence/minhash_lsh.py +182 -0
- algo_cli/intelligence/multi_model_score.py +174 -0
- algo_cli/intelligence/multi_tier_grade.py +211 -0
- algo_cli/intelligence/negative_controls.py +113 -0
- algo_cli/intelligence/numeric_clamp.py +63 -0
- algo_cli/intelligence/occ_editor.py +66 -0
- algo_cli/intelligence/output_normalize.py +112 -0
- algo_cli/intelligence/parallel_delegation.py +98 -0
- algo_cli/intelligence/parallel_fanout.py +104 -0
- algo_cli/intelligence/permission_modes.py +105 -0
- algo_cli/intelligence/pre_push_gate.py +68 -0
- algo_cli/intelligence/prefetch.py +171 -0
- algo_cli/intelligence/process_framework.py +217 -0
- algo_cli/intelligence/project_graph.py +387 -0
- algo_cli/intelligence/query_expansion.py +146 -0
- algo_cli/intelligence/ralph_loop.py +117 -0
- algo_cli/intelligence/rate_limiter.py +153 -0
- algo_cli/intelligence/refactor_transaction.py +94 -0
- algo_cli/intelligence/research_workspace.py +108 -0
- algo_cli/intelligence/retraction_ledger.py +72 -0
- algo_cli/intelligence/saga_pattern.py +88 -0
- algo_cli/intelligence/session_fork.py +100 -0
- algo_cli/intelligence/shadow_editor.py +67 -0
- algo_cli/intelligence/shell_session.py +213 -0
- algo_cli/intelligence/source_registry.py +143 -0
- algo_cli/intelligence/spawn_scales.py +99 -0
- algo_cli/intelligence/stat_stability.py +104 -0
- algo_cli/intelligence/structural_validator.py +148 -0
- algo_cli/intelligence/subagent_spawner.py +111 -0
- algo_cli/intelligence/symmetric_verify.py +70 -0
- algo_cli/intelligence/task_classifier.py +129 -0
- algo_cli/intelligence/team_execution.py +122 -0
- algo_cli/intelligence/tiered_access.py +121 -0
- algo_cli/intelligence/utility_registry.py +159 -0
- algo_cli/intuition_engine.py +560 -0
- algo_cli/intuition_injector.py +82 -0
- algo_cli/kernels/__init__.py +5 -0
- algo_cli/kernels/manifest.py +763 -0
- algo_cli/main.py +3903 -0
- algo_cli/memory_candidates.py +541 -0
- algo_cli/memory_echo_veil.py +394 -0
- algo_cli/memory_runtime.py +112 -0
- algo_cli/model_info.py +548 -0
- algo_cli/model_profile.py +160 -0
- algo_cli/model_routing.py +74 -0
- algo_cli/oneshot.py +331 -0
- algo_cli/perf_telemetry.py +389 -0
- algo_cli/plugins.py +245 -0
- algo_cli/private_event_store.py +654 -0
- algo_cli/quantization/__init__.py +24 -0
- algo_cli/quantization/lloyd_max.py +98 -0
- algo_cli/quantization/turbo_quant.py +308 -0
- algo_cli/reasoning/__init__.py +46 -0
- algo_cli/reasoning/combinatorial.py +356 -0
- algo_cli/reasoning/graph_of_thought.py +297 -0
- algo_cli/reasoning/mcts.py +220 -0
- algo_cli/reasoning/neuro_symbolic.py +250 -0
- algo_cli/reasoning/react.py +246 -0
- algo_cli/reasoning/reflexion.py +225 -0
- algo_cli/reasoning/tree_of_thought.py +241 -0
- algo_cli/reasoning_bridge.py +150 -0
- algo_cli/reconciliation.py +284 -0
- algo_cli/reflex.py +385 -0
- algo_cli/resources/docs/ALGO.md +13958 -0
- algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
- algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
- algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
- algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
- algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
- algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
- algo_cli/resources/docs/main-split-map.md +35 -0
- algo_cli/resources/docs/privacy-and-context.md +48 -0
- algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
- algo_cli/resources/skills/README.md +26 -0
- algo_cli/resources/skills/algo-cli.md +59 -0
- algo_cli/resources/skills/edit-file-precision.md +49 -0
- algo_cli/resources/skills/harness-search-first.md +47 -0
- algo_cli/resources/skills/memory-recall-ritual.md +51 -0
- algo_cli/resources/skills/qol-algorithms.md +224 -0
- algo_cli/resources/skills/smart-error-recovery.md +56 -0
- algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
- algo_cli/retrieval_algorithms.py +127 -0
- algo_cli/runtime_qos.py +236 -0
- algo_cli/runtime_services.py +320 -0
- algo_cli/session_commands.py +95 -0
- algo_cli/session_mode.py +113 -0
- algo_cli/skills.py +430 -0
- algo_cli/slash_dispatch.py +1265 -0
- algo_cli/small_context.py +206 -0
- algo_cli/spawn_budget.py +89 -0
- algo_cli/task_ledger.py +84 -0
- algo_cli/task_router.py +197 -0
- algo_cli/tool_context.py +94 -0
- algo_cli/tool_contract.py +99 -0
- algo_cli/tool_policy.py +357 -0
- algo_cli/tool_runtime.py +647 -0
- algo_cli/tools.py +3056 -0
- algo_cli/url_scheme.py +174 -0
- algo_cli/verify.py +154 -0
- algo_cli/version_manifest.py +178 -0
- algo_cli/vision_screenshot_verify.py +76 -0
- algo_cli/workspace_resolver.py +68 -0
- algo_cli/x_account.py +209 -0
- algo_cli/xai_auth.py +374 -0
- algo_cli/xai_client.py +600 -0
- algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
- algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
- algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
- algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
- algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
- ollama_cli/__init__.py +67 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
"""Bounded, private JSONL persistence for local runtime events.
|
|
2
|
+
|
|
3
|
+
The store deliberately has a small contract:
|
|
4
|
+
|
|
5
|
+
* callers append JSON objects;
|
|
6
|
+
* the store owns the timestamp and on-disk envelope;
|
|
7
|
+
* readers receive only valid, non-expired records;
|
|
8
|
+
* maintenance keeps the newest suffix that satisfies record and byte limits;
|
|
9
|
+
* readiness exposes aggregate metadata, never event content.
|
|
10
|
+
|
|
11
|
+
The implementation uses an advisory sidecar lock so cooperating processes do
|
|
12
|
+
not interleave appends or race an atomic compaction. A completed append is not
|
|
13
|
+
rolled back if the following compaction fails: the new line remains durable and
|
|
14
|
+
the aggregate result reports that maintenance is still required. A later
|
|
15
|
+
append or explicit ``compact`` call can repair the file.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import math
|
|
22
|
+
import os
|
|
23
|
+
import stat
|
|
24
|
+
import tempfile
|
|
25
|
+
import time
|
|
26
|
+
from collections import deque
|
|
27
|
+
from contextlib import contextmanager
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, Callable, Iterator, Mapping
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
STORE_SCHEMA_VERSION = 1
|
|
34
|
+
DEFAULT_MAX_RECORDS = 1_000
|
|
35
|
+
DEFAULT_MAX_BYTES = 4 * 1024 * 1024
|
|
36
|
+
DEFAULT_MAX_AGE_SECONDS = 30 * 24 * 60 * 60
|
|
37
|
+
|
|
38
|
+
_DIRECTORY_MODE = 0o700
|
|
39
|
+
_FILE_MODE = 0o600
|
|
40
|
+
_LOCK_RETRY_SECONDS = 0.025
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PrivateEventStoreError(RuntimeError):
|
|
44
|
+
"""Base error for private event-store operations."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class UnsafeStorePathError(PrivateEventStoreError):
|
|
48
|
+
"""Raised when a store or lock path is not a regular file."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class EventTooLargeError(ValueError):
|
|
52
|
+
"""Raised before writing an event that cannot fit the byte limit."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class RetentionPolicy:
|
|
57
|
+
"""Mandatory record, byte, and age bounds for a private event store."""
|
|
58
|
+
|
|
59
|
+
max_records: int = DEFAULT_MAX_RECORDS
|
|
60
|
+
max_bytes: int = DEFAULT_MAX_BYTES
|
|
61
|
+
max_age_seconds: float = DEFAULT_MAX_AGE_SECONDS
|
|
62
|
+
|
|
63
|
+
def __post_init__(self) -> None:
|
|
64
|
+
if not isinstance(self.max_records, int) or isinstance(self.max_records, bool) or self.max_records < 1:
|
|
65
|
+
raise ValueError("max_records must be a positive integer")
|
|
66
|
+
if not isinstance(self.max_bytes, int) or isinstance(self.max_bytes, bool) or self.max_bytes < 1:
|
|
67
|
+
raise ValueError("max_bytes must be a positive integer")
|
|
68
|
+
if (
|
|
69
|
+
not isinstance(self.max_age_seconds, (int, float))
|
|
70
|
+
or isinstance(self.max_age_seconds, bool)
|
|
71
|
+
or not math.isfinite(float(self.max_age_seconds))
|
|
72
|
+
or self.max_age_seconds <= 0
|
|
73
|
+
):
|
|
74
|
+
raise ValueError("max_age_seconds must be a positive finite number")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class StoredEvent:
|
|
79
|
+
"""One decoded event plus its store-controlled admission timestamp."""
|
|
80
|
+
|
|
81
|
+
stored_at: float
|
|
82
|
+
event: dict[str, Any]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class MaintenanceResult:
|
|
87
|
+
"""Content-free aggregate result from append or compaction maintenance."""
|
|
88
|
+
|
|
89
|
+
stored: bool
|
|
90
|
+
compacted: bool
|
|
91
|
+
retained_records: int
|
|
92
|
+
retained_bytes: int
|
|
93
|
+
file_bytes: int
|
|
94
|
+
dropped_records: int
|
|
95
|
+
malformed_records: int
|
|
96
|
+
retention_satisfied: bool
|
|
97
|
+
error_type: str | None = None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class _ScanResult:
|
|
102
|
+
retained: deque[tuple[StoredEvent, bytes]]
|
|
103
|
+
file_bytes: int = 0
|
|
104
|
+
valid_records: int = 0
|
|
105
|
+
malformed_records: int = 0
|
|
106
|
+
expired_records: int = 0
|
|
107
|
+
dropped_for_bounds: int = 0
|
|
108
|
+
retained_bytes: int = 0
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def dropped_records(self) -> int:
|
|
112
|
+
return self.expired_records + self.dropped_for_bounds
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def compaction_needed(self) -> bool:
|
|
116
|
+
return (
|
|
117
|
+
self.malformed_records > 0
|
|
118
|
+
or self.dropped_records > 0
|
|
119
|
+
or self.file_bytes != self.retained_bytes
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _mode_is(path: Path, expected: int) -> bool | None:
|
|
124
|
+
"""Return exact POSIX mode equality, or ``None`` where it is not meaningful."""
|
|
125
|
+
if os.name != "posix":
|
|
126
|
+
return None
|
|
127
|
+
try:
|
|
128
|
+
return stat.S_IMODE(path.stat().st_mode) == expected
|
|
129
|
+
except OSError:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _ensure_regular_file(path: Path) -> None:
|
|
134
|
+
"""Reject symlinks and special files before following them for private I/O."""
|
|
135
|
+
try:
|
|
136
|
+
info = path.lstat()
|
|
137
|
+
except FileNotFoundError:
|
|
138
|
+
return
|
|
139
|
+
if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode):
|
|
140
|
+
raise UnsafeStorePathError("private store paths must be regular files")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _secure_open_flags(flags: int) -> int:
|
|
144
|
+
flags |= getattr(os, "O_CLOEXEC", 0)
|
|
145
|
+
flags |= getattr(os, "O_NOFOLLOW", 0)
|
|
146
|
+
if os.name == "nt":
|
|
147
|
+
flags |= getattr(os, "O_BINARY", 0)
|
|
148
|
+
return flags
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _fsync_directory(path: Path) -> None:
|
|
152
|
+
"""Best-effort directory sync after atomic publication."""
|
|
153
|
+
if os.name != "posix":
|
|
154
|
+
return
|
|
155
|
+
flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0)
|
|
156
|
+
try:
|
|
157
|
+
descriptor = os.open(path, flags)
|
|
158
|
+
except OSError:
|
|
159
|
+
return
|
|
160
|
+
try:
|
|
161
|
+
os.fsync(descriptor)
|
|
162
|
+
except OSError:
|
|
163
|
+
# Some otherwise valid filesystems do not support directory fsync.
|
|
164
|
+
pass
|
|
165
|
+
finally:
|
|
166
|
+
try:
|
|
167
|
+
os.close(descriptor)
|
|
168
|
+
except OSError:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class PrivateEventStore:
|
|
173
|
+
"""A cross-platform, lock-safe, retention-bounded private JSONL store."""
|
|
174
|
+
|
|
175
|
+
def __init__(
|
|
176
|
+
self,
|
|
177
|
+
path: Path | str,
|
|
178
|
+
*,
|
|
179
|
+
policy: RetentionPolicy | None = None,
|
|
180
|
+
lock_timeout_seconds: float = 10.0,
|
|
181
|
+
clock: Callable[[], float] = time.time,
|
|
182
|
+
) -> None:
|
|
183
|
+
self.path = Path(path)
|
|
184
|
+
self.policy = policy or RetentionPolicy()
|
|
185
|
+
if (
|
|
186
|
+
isinstance(lock_timeout_seconds, bool)
|
|
187
|
+
or not math.isfinite(float(lock_timeout_seconds))
|
|
188
|
+
or lock_timeout_seconds <= 0
|
|
189
|
+
):
|
|
190
|
+
raise ValueError("lock_timeout_seconds must be a positive finite number")
|
|
191
|
+
self.lock_timeout_seconds = float(lock_timeout_seconds)
|
|
192
|
+
self._clock = clock
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def lock_path(self) -> Path:
|
|
196
|
+
return self.path.with_suffix(self.path.suffix + ".lock")
|
|
197
|
+
|
|
198
|
+
def _ensure_private_directory(self, *, repair_permissions: bool = True) -> None:
|
|
199
|
+
parent = self.path.parent
|
|
200
|
+
parent.mkdir(mode=_DIRECTORY_MODE, parents=True, exist_ok=True)
|
|
201
|
+
if not parent.is_dir():
|
|
202
|
+
raise UnsafeStorePathError("private store parent must be a directory")
|
|
203
|
+
if repair_permissions and os.name == "posix":
|
|
204
|
+
os.chmod(parent, _DIRECTORY_MODE)
|
|
205
|
+
|
|
206
|
+
def _repair_file_mode(self, path: Path) -> None:
|
|
207
|
+
_ensure_regular_file(path)
|
|
208
|
+
if os.name == "posix" and path.exists():
|
|
209
|
+
os.chmod(path, _FILE_MODE)
|
|
210
|
+
|
|
211
|
+
@contextmanager
|
|
212
|
+
def _locked(self, *, repair_permissions: bool = True) -> Iterator[None]:
|
|
213
|
+
"""Take an advisory lock without growing the sidecar on each use."""
|
|
214
|
+
self._ensure_private_directory(repair_permissions=repair_permissions)
|
|
215
|
+
_ensure_regular_file(self.lock_path)
|
|
216
|
+
descriptor = os.open(
|
|
217
|
+
self.lock_path,
|
|
218
|
+
_secure_open_flags(os.O_RDWR | os.O_CREAT),
|
|
219
|
+
_FILE_MODE,
|
|
220
|
+
)
|
|
221
|
+
try:
|
|
222
|
+
if repair_permissions and os.name == "posix":
|
|
223
|
+
os.fchmod(descriptor, _FILE_MODE)
|
|
224
|
+
# msvcrt locks a byte range and needs at least one byte to exist.
|
|
225
|
+
if os.fstat(descriptor).st_size == 0:
|
|
226
|
+
os.write(descriptor, b"\0")
|
|
227
|
+
os.fsync(descriptor)
|
|
228
|
+
|
|
229
|
+
deadline = time.monotonic() + self.lock_timeout_seconds
|
|
230
|
+
if os.name == "nt":
|
|
231
|
+
import msvcrt
|
|
232
|
+
|
|
233
|
+
lock_region = getattr(msvcrt, "locking")
|
|
234
|
+
lock_nonblocking = getattr(msvcrt, "LK_NBLCK")
|
|
235
|
+
unlock = getattr(msvcrt, "LK_UNLCK")
|
|
236
|
+
while True:
|
|
237
|
+
try:
|
|
238
|
+
os.lseek(descriptor, 0, os.SEEK_SET)
|
|
239
|
+
lock_region(descriptor, lock_nonblocking, 1)
|
|
240
|
+
break
|
|
241
|
+
except OSError:
|
|
242
|
+
if time.monotonic() >= deadline:
|
|
243
|
+
raise TimeoutError("timed out waiting for private event-store lock")
|
|
244
|
+
time.sleep(_LOCK_RETRY_SECONDS)
|
|
245
|
+
try:
|
|
246
|
+
if os.fstat(descriptor).st_size != 1:
|
|
247
|
+
os.ftruncate(descriptor, 1)
|
|
248
|
+
os.fsync(descriptor)
|
|
249
|
+
yield
|
|
250
|
+
finally:
|
|
251
|
+
os.lseek(descriptor, 0, os.SEEK_SET)
|
|
252
|
+
lock_region(descriptor, unlock, 1)
|
|
253
|
+
else:
|
|
254
|
+
import fcntl
|
|
255
|
+
|
|
256
|
+
while True:
|
|
257
|
+
try:
|
|
258
|
+
fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
259
|
+
break
|
|
260
|
+
except BlockingIOError:
|
|
261
|
+
if time.monotonic() >= deadline:
|
|
262
|
+
raise TimeoutError("timed out waiting for private event-store lock")
|
|
263
|
+
time.sleep(_LOCK_RETRY_SECONDS)
|
|
264
|
+
try:
|
|
265
|
+
if os.fstat(descriptor).st_size != 1:
|
|
266
|
+
os.ftruncate(descriptor, 1)
|
|
267
|
+
os.fsync(descriptor)
|
|
268
|
+
yield
|
|
269
|
+
finally:
|
|
270
|
+
fcntl.flock(descriptor, fcntl.LOCK_UN)
|
|
271
|
+
finally:
|
|
272
|
+
os.close(descriptor)
|
|
273
|
+
|
|
274
|
+
def initialize(self) -> None:
|
|
275
|
+
"""Create an empty private store and repair its directory/file modes."""
|
|
276
|
+
with self._locked():
|
|
277
|
+
self._repair_file_mode(self.path)
|
|
278
|
+
created = not self.path.exists()
|
|
279
|
+
descriptor = os.open(
|
|
280
|
+
self.path,
|
|
281
|
+
_secure_open_flags(os.O_WRONLY | os.O_APPEND | os.O_CREAT),
|
|
282
|
+
_FILE_MODE,
|
|
283
|
+
)
|
|
284
|
+
try:
|
|
285
|
+
if os.name == "posix":
|
|
286
|
+
os.fchmod(descriptor, _FILE_MODE)
|
|
287
|
+
os.fsync(descriptor)
|
|
288
|
+
finally:
|
|
289
|
+
os.close(descriptor)
|
|
290
|
+
if created:
|
|
291
|
+
_fsync_directory(self.path.parent)
|
|
292
|
+
|
|
293
|
+
def repair_permissions(self) -> None:
|
|
294
|
+
"""Repair the store directory, data file, and lock file when present."""
|
|
295
|
+
with self._locked():
|
|
296
|
+
self._repair_file_mode(self.path)
|
|
297
|
+
self._repair_file_mode(self.lock_path)
|
|
298
|
+
|
|
299
|
+
@staticmethod
|
|
300
|
+
def _encode_event(event: Mapping[str, Any], stored_at: float) -> bytes:
|
|
301
|
+
envelope = {
|
|
302
|
+
"version": STORE_SCHEMA_VERSION,
|
|
303
|
+
"stored_at": stored_at,
|
|
304
|
+
"event": dict(event),
|
|
305
|
+
}
|
|
306
|
+
try:
|
|
307
|
+
text = json.dumps(
|
|
308
|
+
envelope,
|
|
309
|
+
ensure_ascii=False,
|
|
310
|
+
allow_nan=False,
|
|
311
|
+
separators=(",", ":"),
|
|
312
|
+
sort_keys=True,
|
|
313
|
+
)
|
|
314
|
+
except (TypeError, ValueError) as exc:
|
|
315
|
+
raise ValueError("event must be a finite JSON object") from exc
|
|
316
|
+
return text.encode("utf-8") + b"\n"
|
|
317
|
+
|
|
318
|
+
@staticmethod
|
|
319
|
+
def _decode_line(raw: bytes) -> StoredEvent | None:
|
|
320
|
+
try:
|
|
321
|
+
item = json.loads(raw.decode("utf-8"))
|
|
322
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
323
|
+
return None
|
|
324
|
+
if not isinstance(item, dict) or item.get("version") != STORE_SCHEMA_VERSION:
|
|
325
|
+
return None
|
|
326
|
+
stored_at = item.get("stored_at")
|
|
327
|
+
event = item.get("event")
|
|
328
|
+
if (
|
|
329
|
+
isinstance(stored_at, bool)
|
|
330
|
+
or not isinstance(stored_at, (int, float))
|
|
331
|
+
or not math.isfinite(float(stored_at))
|
|
332
|
+
or float(stored_at) < 0
|
|
333
|
+
or not isinstance(event, dict)
|
|
334
|
+
):
|
|
335
|
+
return None
|
|
336
|
+
return StoredEvent(stored_at=float(stored_at), event=event)
|
|
337
|
+
|
|
338
|
+
@classmethod
|
|
339
|
+
def _encode_stored(cls, stored: StoredEvent) -> bytes:
|
|
340
|
+
return cls._encode_event(stored.event, stored.stored_at)
|
|
341
|
+
|
|
342
|
+
def _scan(self, *, now: float) -> _ScanResult:
|
|
343
|
+
result = _ScanResult(retained=deque())
|
|
344
|
+
_ensure_regular_file(self.path)
|
|
345
|
+
try:
|
|
346
|
+
result.file_bytes = self.path.stat().st_size
|
|
347
|
+
except FileNotFoundError:
|
|
348
|
+
return result
|
|
349
|
+
|
|
350
|
+
cutoff = now - float(self.policy.max_age_seconds)
|
|
351
|
+
retained_bytes = 0
|
|
352
|
+
with self.path.open("rb") as handle:
|
|
353
|
+
for raw in handle:
|
|
354
|
+
if not raw.strip():
|
|
355
|
+
# Empty separators can safely delimit a previously torn tail.
|
|
356
|
+
continue
|
|
357
|
+
stored = self._decode_line(raw)
|
|
358
|
+
if stored is None:
|
|
359
|
+
result.malformed_records += 1
|
|
360
|
+
continue
|
|
361
|
+
result.valid_records += 1
|
|
362
|
+
try:
|
|
363
|
+
# Python's decoder accepts non-standard NaN/Infinity
|
|
364
|
+
# tokens. Requiring canonical finite JSON here keeps such
|
|
365
|
+
# externally injected lines in the malformed bucket.
|
|
366
|
+
encoded = self._encode_stored(stored)
|
|
367
|
+
except (TypeError, ValueError, RecursionError):
|
|
368
|
+
result.valid_records -= 1
|
|
369
|
+
result.malformed_records += 1
|
|
370
|
+
continue
|
|
371
|
+
if stored.stored_at < cutoff:
|
|
372
|
+
result.expired_records += 1
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
result.retained.append((stored, encoded))
|
|
376
|
+
retained_bytes += len(encoded)
|
|
377
|
+
while (
|
|
378
|
+
len(result.retained) > self.policy.max_records
|
|
379
|
+
or retained_bytes > self.policy.max_bytes
|
|
380
|
+
):
|
|
381
|
+
_discarded, discarded_bytes = result.retained.popleft()
|
|
382
|
+
retained_bytes -= len(discarded_bytes)
|
|
383
|
+
result.dropped_for_bounds += 1
|
|
384
|
+
|
|
385
|
+
result.retained_bytes = retained_bytes
|
|
386
|
+
return result
|
|
387
|
+
|
|
388
|
+
def _append_bytes(self, payload: bytes) -> None:
|
|
389
|
+
"""Append all bytes and fsync; a torn failure is tolerated on next use."""
|
|
390
|
+
self._repair_file_mode(self.path)
|
|
391
|
+
created = not self.path.exists()
|
|
392
|
+
descriptor = os.open(
|
|
393
|
+
self.path,
|
|
394
|
+
_secure_open_flags(os.O_WRONLY | os.O_APPEND | os.O_CREAT),
|
|
395
|
+
_FILE_MODE,
|
|
396
|
+
)
|
|
397
|
+
try:
|
|
398
|
+
if os.name == "posix":
|
|
399
|
+
os.fchmod(descriptor, _FILE_MODE)
|
|
400
|
+
view = memoryview(payload)
|
|
401
|
+
while view:
|
|
402
|
+
written = os.write(descriptor, view)
|
|
403
|
+
if written <= 0:
|
|
404
|
+
raise OSError("private event-store append made no progress")
|
|
405
|
+
view = view[written:]
|
|
406
|
+
os.fsync(descriptor)
|
|
407
|
+
finally:
|
|
408
|
+
os.close(descriptor)
|
|
409
|
+
if created:
|
|
410
|
+
_fsync_directory(self.path.parent)
|
|
411
|
+
|
|
412
|
+
def _needs_separator(self) -> bool:
|
|
413
|
+
try:
|
|
414
|
+
size = self.path.stat().st_size
|
|
415
|
+
except FileNotFoundError:
|
|
416
|
+
return False
|
|
417
|
+
if size == 0:
|
|
418
|
+
return False
|
|
419
|
+
with self.path.open("rb") as handle:
|
|
420
|
+
handle.seek(-1, os.SEEK_END)
|
|
421
|
+
# JSONL records are LF-delimited. A lone CR may be the first half
|
|
422
|
+
# of a torn CRLF, so prefix LF to close it before the new record.
|
|
423
|
+
return handle.read(1) != b"\n"
|
|
424
|
+
|
|
425
|
+
def _atomic_replace(self, lines: Iterator[bytes]) -> None:
|
|
426
|
+
"""Publish compacted JSONL without exposing a partially written file."""
|
|
427
|
+
self._ensure_private_directory()
|
|
428
|
+
descriptor, temporary_name = tempfile.mkstemp(
|
|
429
|
+
prefix=f".{self.path.name}.",
|
|
430
|
+
suffix=".tmp",
|
|
431
|
+
dir=str(self.path.parent),
|
|
432
|
+
)
|
|
433
|
+
temporary = Path(temporary_name)
|
|
434
|
+
try:
|
|
435
|
+
if os.name == "posix":
|
|
436
|
+
os.fchmod(descriptor, _FILE_MODE)
|
|
437
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
438
|
+
for line in lines:
|
|
439
|
+
handle.write(line)
|
|
440
|
+
handle.flush()
|
|
441
|
+
os.fsync(handle.fileno())
|
|
442
|
+
os.replace(temporary, self.path)
|
|
443
|
+
# The temporary inode already has the final private mode. Keep
|
|
444
|
+
# publication as the last fallible state transition so an error
|
|
445
|
+
# before replace always leaves the old file intact.
|
|
446
|
+
_fsync_directory(self.path.parent)
|
|
447
|
+
except BaseException:
|
|
448
|
+
try:
|
|
449
|
+
os.close(descriptor)
|
|
450
|
+
except OSError:
|
|
451
|
+
pass
|
|
452
|
+
try:
|
|
453
|
+
temporary.unlink()
|
|
454
|
+
except OSError:
|
|
455
|
+
pass
|
|
456
|
+
raise
|
|
457
|
+
|
|
458
|
+
def _maintenance_result(
|
|
459
|
+
self,
|
|
460
|
+
scan: _ScanResult,
|
|
461
|
+
*,
|
|
462
|
+
stored: bool,
|
|
463
|
+
compacted: bool,
|
|
464
|
+
error_type: str | None = None,
|
|
465
|
+
) -> MaintenanceResult:
|
|
466
|
+
if compacted:
|
|
467
|
+
file_bytes = scan.retained_bytes
|
|
468
|
+
else:
|
|
469
|
+
try:
|
|
470
|
+
file_bytes = self.path.stat().st_size
|
|
471
|
+
except OSError:
|
|
472
|
+
file_bytes = scan.file_bytes
|
|
473
|
+
retention_satisfied = (
|
|
474
|
+
error_type is None
|
|
475
|
+
and len(scan.retained) <= self.policy.max_records
|
|
476
|
+
and scan.retained_bytes <= self.policy.max_bytes
|
|
477
|
+
and (compacted or not scan.compaction_needed)
|
|
478
|
+
)
|
|
479
|
+
return MaintenanceResult(
|
|
480
|
+
stored=stored,
|
|
481
|
+
compacted=compacted,
|
|
482
|
+
retained_records=len(scan.retained),
|
|
483
|
+
retained_bytes=scan.retained_bytes,
|
|
484
|
+
file_bytes=file_bytes,
|
|
485
|
+
dropped_records=scan.dropped_records,
|
|
486
|
+
malformed_records=scan.malformed_records,
|
|
487
|
+
retention_satisfied=retention_satisfied,
|
|
488
|
+
error_type=error_type,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def append(self, event: Mapping[str, Any]) -> MaintenanceResult:
|
|
492
|
+
"""Durably append one event, then atomically enforce retention.
|
|
493
|
+
|
|
494
|
+
Serialization and single-record size validation happen before touching
|
|
495
|
+
the file. If append succeeds but compaction raises an ``OSError``, the
|
|
496
|
+
append is kept and the returned result names only the exception type.
|
|
497
|
+
"""
|
|
498
|
+
if not isinstance(event, Mapping):
|
|
499
|
+
raise TypeError("event must be a mapping")
|
|
500
|
+
now = float(self._clock())
|
|
501
|
+
if not math.isfinite(now) or now < 0:
|
|
502
|
+
raise ValueError("clock must return a finite non-negative timestamp")
|
|
503
|
+
encoded = self._encode_event(event, now)
|
|
504
|
+
if len(encoded) > self.policy.max_bytes:
|
|
505
|
+
raise EventTooLargeError("encoded event exceeds private store max_bytes")
|
|
506
|
+
|
|
507
|
+
with self._locked():
|
|
508
|
+
# Delimit an old torn tail so this complete event remains recoverable.
|
|
509
|
+
payload = (b"\n" if self._needs_separator() else b"") + encoded
|
|
510
|
+
self._append_bytes(payload)
|
|
511
|
+
scan = self._scan(now=now)
|
|
512
|
+
if not scan.compaction_needed:
|
|
513
|
+
return self._maintenance_result(scan, stored=True, compacted=False)
|
|
514
|
+
try:
|
|
515
|
+
self._atomic_replace(line for _stored, line in scan.retained)
|
|
516
|
+
except Exception as exc:
|
|
517
|
+
# Do not report append failure after its fsync: that invites a
|
|
518
|
+
# retry and duplicate. Surface bounded, content-free state.
|
|
519
|
+
return self._maintenance_result(
|
|
520
|
+
scan,
|
|
521
|
+
stored=True,
|
|
522
|
+
compacted=False,
|
|
523
|
+
error_type=type(exc).__name__,
|
|
524
|
+
)
|
|
525
|
+
return self._maintenance_result(scan, stored=True, compacted=True)
|
|
526
|
+
|
|
527
|
+
def compact(self) -> MaintenanceResult:
|
|
528
|
+
"""Atomically remove malformed, expired, and out-of-bound records."""
|
|
529
|
+
now = float(self._clock())
|
|
530
|
+
if not math.isfinite(now) or now < 0:
|
|
531
|
+
raise ValueError("clock must return a finite non-negative timestamp")
|
|
532
|
+
with self._locked():
|
|
533
|
+
self._repair_file_mode(self.path)
|
|
534
|
+
scan = self._scan(now=now)
|
|
535
|
+
if not scan.compaction_needed:
|
|
536
|
+
return self._maintenance_result(scan, stored=False, compacted=False)
|
|
537
|
+
self._atomic_replace(line for _stored, line in scan.retained)
|
|
538
|
+
return self._maintenance_result(scan, stored=False, compacted=True)
|
|
539
|
+
|
|
540
|
+
def read_records(self, *, limit: int | None = None) -> list[StoredEvent]:
|
|
541
|
+
"""Read the valid retained suffix without returning malformed content."""
|
|
542
|
+
if limit is not None and (
|
|
543
|
+
not isinstance(limit, int) or isinstance(limit, bool) or limit < 0
|
|
544
|
+
):
|
|
545
|
+
raise ValueError("limit must be a non-negative integer or None")
|
|
546
|
+
if limit == 0:
|
|
547
|
+
return []
|
|
548
|
+
now = float(self._clock())
|
|
549
|
+
if not math.isfinite(now) or now < 0:
|
|
550
|
+
raise ValueError("clock must return a finite non-negative timestamp")
|
|
551
|
+
with self._locked():
|
|
552
|
+
self._repair_file_mode(self.path)
|
|
553
|
+
scan = self._scan(now=now)
|
|
554
|
+
records = [stored for stored, _line in scan.retained]
|
|
555
|
+
if limit is not None:
|
|
556
|
+
return records[-limit:]
|
|
557
|
+
return records
|
|
558
|
+
|
|
559
|
+
def read_events(self, *, limit: int | None = None) -> list[dict[str, Any]]:
|
|
560
|
+
"""Convenience view returning event objects without store metadata."""
|
|
561
|
+
return [record.event for record in self.read_records(limit=limit)]
|
|
562
|
+
|
|
563
|
+
def readiness(self, *, repair_permissions: bool = True) -> dict[str, Any]:
|
|
564
|
+
"""Return content-free aggregate health and retention metadata.
|
|
565
|
+
|
|
566
|
+
Permission repair is enabled by default because broad permissions on a
|
|
567
|
+
private store are a live safety defect. No data file is created merely
|
|
568
|
+
by checking readiness; the private directory and lock sidecar may be.
|
|
569
|
+
"""
|
|
570
|
+
base: dict[str, Any] = {
|
|
571
|
+
"schema_version": STORE_SCHEMA_VERSION,
|
|
572
|
+
"initialized": False,
|
|
573
|
+
"records": 0,
|
|
574
|
+
"retained_records": 0,
|
|
575
|
+
"file_bytes": 0,
|
|
576
|
+
"retained_bytes": 0,
|
|
577
|
+
"malformed_records": 0,
|
|
578
|
+
"expired_records": 0,
|
|
579
|
+
"dropped_for_bounds": 0,
|
|
580
|
+
"compaction_needed": False,
|
|
581
|
+
"permissions_enforced": os.name == "posix",
|
|
582
|
+
"directory_private": None,
|
|
583
|
+
"file_private": None,
|
|
584
|
+
"lock_private": None,
|
|
585
|
+
"limits": {
|
|
586
|
+
"max_records": self.policy.max_records,
|
|
587
|
+
"max_bytes": self.policy.max_bytes,
|
|
588
|
+
"max_age_seconds": self.policy.max_age_seconds,
|
|
589
|
+
},
|
|
590
|
+
"error_type": None,
|
|
591
|
+
}
|
|
592
|
+
try:
|
|
593
|
+
if repair_permissions:
|
|
594
|
+
self._ensure_private_directory()
|
|
595
|
+
elif not self.path.parent.exists():
|
|
596
|
+
return {**base, "status": "empty"}
|
|
597
|
+
|
|
598
|
+
with self._locked(repair_permissions=repair_permissions):
|
|
599
|
+
if repair_permissions:
|
|
600
|
+
self._repair_file_mode(self.path)
|
|
601
|
+
self._repair_file_mode(self.lock_path)
|
|
602
|
+
now = float(self._clock())
|
|
603
|
+
if not math.isfinite(now) or now < 0:
|
|
604
|
+
raise ValueError("clock must return a finite non-negative timestamp")
|
|
605
|
+
scan = self._scan(now=now)
|
|
606
|
+
|
|
607
|
+
initialized = self.path.exists()
|
|
608
|
+
directory_private = _mode_is(self.path.parent, _DIRECTORY_MODE)
|
|
609
|
+
file_private = _mode_is(self.path, _FILE_MODE) if initialized else None
|
|
610
|
+
lock_private = _mode_is(self.lock_path, _FILE_MODE)
|
|
611
|
+
permission_problem = os.name == "posix" and (
|
|
612
|
+
directory_private is not True
|
|
613
|
+
or lock_private is not True
|
|
614
|
+
or (initialized and file_private is not True)
|
|
615
|
+
)
|
|
616
|
+
degraded = scan.compaction_needed or permission_problem
|
|
617
|
+
status = "degraded" if degraded else ("ready" if initialized else "empty")
|
|
618
|
+
return {
|
|
619
|
+
**base,
|
|
620
|
+
"status": status,
|
|
621
|
+
"initialized": initialized,
|
|
622
|
+
"records": scan.valid_records,
|
|
623
|
+
"retained_records": len(scan.retained),
|
|
624
|
+
"file_bytes": scan.file_bytes,
|
|
625
|
+
"retained_bytes": scan.retained_bytes,
|
|
626
|
+
"malformed_records": scan.malformed_records,
|
|
627
|
+
"expired_records": scan.expired_records,
|
|
628
|
+
"dropped_for_bounds": scan.dropped_for_bounds,
|
|
629
|
+
"compaction_needed": scan.compaction_needed,
|
|
630
|
+
"directory_private": directory_private,
|
|
631
|
+
"file_private": file_private,
|
|
632
|
+
"lock_private": lock_private,
|
|
633
|
+
}
|
|
634
|
+
except Exception as exc:
|
|
635
|
+
return {
|
|
636
|
+
**base,
|
|
637
|
+
"status": "error",
|
|
638
|
+
"error_type": type(exc).__name__,
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
__all__ = [
|
|
643
|
+
"DEFAULT_MAX_AGE_SECONDS",
|
|
644
|
+
"DEFAULT_MAX_BYTES",
|
|
645
|
+
"DEFAULT_MAX_RECORDS",
|
|
646
|
+
"EventTooLargeError",
|
|
647
|
+
"MaintenanceResult",
|
|
648
|
+
"PrivateEventStore",
|
|
649
|
+
"PrivateEventStoreError",
|
|
650
|
+
"RetentionPolicy",
|
|
651
|
+
"STORE_SCHEMA_VERSION",
|
|
652
|
+
"StoredEvent",
|
|
653
|
+
"UnsafeStorePathError",
|
|
654
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""TurboQuant-inspired vector quantization for agent memory and RAG compression.
|
|
2
|
+
|
|
3
|
+
Implements PolarQuant/TurboQuant-MSE (random rotation + optimal scalar quantization)
|
|
4
|
+
and QJL residual correction for inner-product preservation, based on arXiv:2504.19874.
|
|
5
|
+
|
|
6
|
+
Local-first: uses NumPy for all operations. No external ML frameworks required.
|
|
7
|
+
TurboVec (Rust-accelerated) is used when available for production throughput.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .turbo_quant import TurboQuantMSE, TurboQuantIP
|
|
11
|
+
from .lloyd_max import precompute_codebook, beta_distribution_pdf
|
|
12
|
+
|
|
13
|
+
# High-level API
|
|
14
|
+
from .turbo_quant import create_vector_index, compress_embeddings, decompress_embeddings
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"TurboQuantMSE",
|
|
18
|
+
"TurboQuantIP",
|
|
19
|
+
"precompute_codebook",
|
|
20
|
+
"beta_distribution_pdf",
|
|
21
|
+
"create_vector_index",
|
|
22
|
+
"compress_embeddings",
|
|
23
|
+
"decompress_embeddings",
|
|
24
|
+
]
|