clm-kernel 0.0.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.
Files changed (333) hide show
  1. clm/__init__.py +230 -0
  2. clm/__main__.py +25 -0
  3. clm/cross_cutting/__init__.py +50 -0
  4. clm/cross_cutting/_test_runner.py +36 -0
  5. clm/cross_cutting/async_utils.py +25 -0
  6. clm/cross_cutting/cel_builder.py +123 -0
  7. clm/cross_cutting/cel_converter.py +697 -0
  8. clm/cross_cutting/cel_engine.py +252 -0
  9. clm/cross_cutting/cel_ops.py +72 -0
  10. clm/cross_cutting/cel_ops_db.py +157 -0
  11. clm/cross_cutting/cel_ops_files.py +99 -0
  12. clm/cross_cutting/cel_ops_state.py +71 -0
  13. clm/cross_cutting/cel_ops_strings.py +64 -0
  14. clm/cross_cutting/cel_resolver.py +63 -0
  15. clm/cross_cutting/cel_slices.py +56 -0
  16. clm/cross_cutting/cel_store.py +330 -0
  17. clm/cross_cutting/clm_logger.py +140 -0
  18. clm/cross_cutting/config/__init__.py +155 -0
  19. clm/cross_cutting/config/config_constants.py +392 -0
  20. clm/cross_cutting/config/env_parameters.py +90 -0
  21. clm/cross_cutting/config/logging.py +378 -0
  22. clm/cross_cutting/config/settings.py +504 -0
  23. clm/cross_cutting/context.py +5 -0
  24. clm/cross_cutting/domain_models.py +55 -0
  25. clm/cross_cutting/domain_types.py +61 -0
  26. clm/cross_cutting/effects.py +67 -0
  27. clm/cross_cutting/errors.py +153 -0
  28. clm/cross_cutting/governance.py +331 -0
  29. clm/cross_cutting/guards.py +41 -0
  30. clm/cross_cutting/mcard.py +321 -0
  31. clm/cross_cutting/native.py +185 -0
  32. clm/cross_cutting/observability.py +289 -0
  33. clm/cross_cutting/pcard.py +434 -0
  34. clm/cross_cutting/registry.py +71 -0
  35. clm/cross_cutting/resources.py +84 -0
  36. clm/cross_cutting/secrets.py +44 -0
  37. clm/cross_cutting/subprocess_utils.py +118 -0
  38. clm/cross_cutting/telemetry.py +164 -0
  39. clm/cross_cutting/testing/__init__.py +8 -0
  40. clm/cross_cutting/testing/comparator.py +187 -0
  41. clm/cross_cutting/testing/datasets.py +87 -0
  42. clm/cross_cutting/timeutil.py +20 -0
  43. clm/cross_cutting/types/new_type_check.py +0 -0
  44. clm/cross_cutting/utils/__init__.py +1 -0
  45. clm/cross_cutting/utils/url_safety.py +50 -0
  46. clm/cross_cutting/vcard.py +560 -0
  47. clm/cross_cutting/writer.py +118 -0
  48. clm/layer0/README.md +50 -0
  49. clm/layer0/__init__.py +223 -0
  50. clm/layer0/algebra.py +60 -0
  51. clm/layer0/algebra_baldwin.py +133 -0
  52. clm/layer0/algebra_baldwin_generators.py +36 -0
  53. clm/layer0/algebra_equivalence.py +262 -0
  54. clm/layer0/algebra_ingest.py +306 -0
  55. clm/layer0/algebra_manifest_io.py +156 -0
  56. clm/layer0/assembler.py +481 -0
  57. clm/layer0/cel/__init__.py +23 -0
  58. clm/layer0/cel_eval.py +491 -0
  59. clm/layer0/codec.py +106 -0
  60. clm/layer0/context.py +122 -0
  61. clm/layer0/db.py +160 -0
  62. clm/layer0/exceptions.py +73 -0
  63. clm/layer0/fibration.py +106 -0
  64. clm/layer0/gates.py +50 -0
  65. clm/layer0/hash.py +80 -0
  66. clm/layer0/loader.py +826 -0
  67. clm/layer0/math_utils.py +163 -0
  68. clm/layer0/mcard.py +96 -0
  69. clm/layer0/model/__init__.py +0 -0
  70. clm/layer0/model/action.py +223 -0
  71. clm/layer0/model/card.py +280 -0
  72. clm/layer0/model/card_triad.py +427 -0
  73. clm/layer0/model/compatibility.py +60 -0
  74. clm/layer0/model/dictionary.py +46 -0
  75. clm/layer0/model/dots.py +331 -0
  76. clm/layer0/model/event_producer.py +88 -0
  77. clm/layer0/model/g_time.py +89 -0
  78. clm/layer0/model/handle.py +163 -0
  79. clm/layer0/model/hash/__init__.py +6 -0
  80. clm/layer0/model/hash/algorithms/__init__.py +3 -0
  81. clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
  82. clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
  83. clm/layer0/model/hash/constants.py +53 -0
  84. clm/layer0/model/hash/enums.py +60 -0
  85. clm/layer0/model/hash/validator.py +307 -0
  86. clm/layer0/model/interpreter.py +275 -0
  87. clm/layer0/model/pagination.py +45 -0
  88. clm/layer0/model/pcard.py +716 -0
  89. clm/layer0/model/schema.py +44 -0
  90. clm/layer0/model/utils/__init__.py +1 -0
  91. clm/layer0/model/utils/content_analyzer.py +152 -0
  92. clm/layer0/model/validators/__init__.py +1 -0
  93. clm/layer0/model/validators/base_validator.py +44 -0
  94. clm/layer0/model/validators/binary_validator.py +66 -0
  95. clm/layer0/model/validators/text_validator.py +82 -0
  96. clm/layer0/model/validators/validation_registry.py +49 -0
  97. clm/layer0/model/vcard.py +823 -0
  98. clm/layer0/model/vcard_ext/__init__.py +39 -0
  99. clm/layer0/model/vcard_ext/core.py +60 -0
  100. clm/layer0/model/vcard_ext/network.py +23 -0
  101. clm/layer0/model/vcard_ext/observability.py +63 -0
  102. clm/layer0/model/vcard_ext/storage.py +60 -0
  103. clm/layer0/model/vcard_ext/vendors.py +211 -0
  104. clm/layer0/model/vcard_sandwich.py +119 -0
  105. clm/layer0/model/vcard_vocabulary.py +463 -0
  106. clm/layer0/model/workflow.py +274 -0
  107. clm/layer0/narrative.py +431 -0
  108. clm/layer0/ontology/collection.py +125 -0
  109. clm/layer0/parser.py +429 -0
  110. clm/layer0/relations.py +73 -0
  111. clm/layer0/rule_evaluator.py +251 -0
  112. clm/layer0/schema/__init__.py +38 -0
  113. clm/layer0/schema/ddl.py +97 -0
  114. clm/layer0/templates/abstract.yaml +99 -0
  115. clm/layer0/templates/balanced.yaml +253 -0
  116. clm/layer0/templates/concrete.yaml +172 -0
  117. clm/layer0/type_registry.py +223 -0
  118. clm/layer0/types.py +204 -0
  119. clm/layer0/utils.py +886 -0
  120. clm/layer0/verifier_core.py +262 -0
  121. clm/layer1/__init__.py +31 -0
  122. clm/layer1/action_dispatcher.py +275 -0
  123. clm/layer1/adapter_registry.py +233 -0
  124. clm/layer1/algebra_baldwin_generators.py +192 -0
  125. clm/layer1/arrow_compiler.py +525 -0
  126. clm/layer1/batch_processor.py +152 -0
  127. clm/layer1/cel/__init__.py +27 -0
  128. clm/layer1/cel/builder.py +173 -0
  129. clm/layer1/engine.py +759 -0
  130. clm/layer1/engine_async.py +89 -0
  131. clm/layer1/eval_callback.py +49 -0
  132. clm/layer1/gate_composer.py +229 -0
  133. clm/layer1/hoare_sandwich.py +298 -0
  134. clm/layer1/identity_guard.py +348 -0
  135. clm/layer1/matrix.py +138 -0
  136. clm/layer1/neural_runtime.py +221 -0
  137. clm/layer1/operations/__init__.py +123 -0
  138. clm/layer1/operations/augment.py +156 -0
  139. clm/layer1/operations/base.py +109 -0
  140. clm/layer1/operations/builtins.py +338 -0
  141. clm/layer1/operations/dispatch.py +144 -0
  142. clm/layer1/operations/exclude.py +107 -0
  143. clm/layer1/operations/handle.py +197 -0
  144. clm/layer1/operations/identity.py +139 -0
  145. clm/layer1/operations/invert.py +66 -0
  146. clm/layer1/operations/loader.py +116 -0
  147. clm/layer1/operations/manipulators/__init__.py +23 -0
  148. clm/layer1/operations/manipulators/document.py +211 -0
  149. clm/layer1/operations/manipulators/media.py +134 -0
  150. clm/layer1/operations/manipulators/structured.py +159 -0
  151. clm/layer1/operations/manipulators/tabular.py +334 -0
  152. clm/layer1/operations/port.py +65 -0
  153. clm/layer1/operations/services.py +529 -0
  154. clm/layer1/operations/split.py +117 -0
  155. clm/layer1/operations/substitute.py +75 -0
  156. clm/layer1/petri.py +1052 -0
  157. clm/layer1/pocketflow_orchestrator.py +408 -0
  158. clm/layer1/pocketflow_scheduler.py +519 -0
  159. clm/layer1/protocols.py +197 -0
  160. clm/layer1/runtime_adapter.py +718 -0
  161. clm/layer1/runtimes/__init__.py +71 -0
  162. clm/layer1/runtimes/_generated_assertions.py +125 -0
  163. clm/layer1/runtimes/_generated_types.py +42 -0
  164. clm/layer1/runtimes/base.py +265 -0
  165. clm/layer1/runtimes/binary.py +101 -0
  166. clm/layer1/runtimes/factory.py +203 -0
  167. clm/layer1/runtimes/javascript.py +321 -0
  168. clm/layer1/runtimes/javascript_runtime.js +33 -0
  169. clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
  170. clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
  171. clm/layer1/runtimes/lambda_calc.py +126 -0
  172. clm/layer1/runtimes/python.py +288 -0
  173. clm/layer1/runtimes/python_runtime.py +41 -0
  174. clm/layer1/runtimes/script.py +114 -0
  175. clm/layer1/sandbox.py +334 -0
  176. clm/layer1/savepoint_guard.py +136 -0
  177. clm/layer1/sparse_net.py +122 -0
  178. clm/layer1/spec_tester.py +165 -0
  179. clm/layer1/store.py +295 -0
  180. clm/layer1/test_runner.py +498 -0
  181. clm/layer1/type_inspector.py +166 -0
  182. clm/layer1/verifier.py +772 -0
  183. clm/layer1/vm.py +91 -0
  184. clm/layer2/__init__.py +78 -0
  185. clm/layer2/card_collection.py +678 -0
  186. clm/layer2/classifier.py +479 -0
  187. clm/layer2/engine/__init__.py +1 -0
  188. clm/layer2/engine/abstract_sql_engine.py +320 -0
  189. clm/layer2/engine/base.py +193 -0
  190. clm/layer2/engine/duckdb_engine.py +576 -0
  191. clm/layer2/engine/sqlite_engine.py +518 -0
  192. clm/layer2/fiber/__init__.py +31 -0
  193. clm/layer2/fiber/context.py +89 -0
  194. clm/layer2/fiber/disposable.py +163 -0
  195. clm/layer2/fiber/fiber.py +125 -0
  196. clm/layer2/fiber/hoare.py +191 -0
  197. clm/layer2/file_io.py +376 -0
  198. clm/layer2/file_registrar.py +77 -0
  199. clm/layer2/ingest.py +202 -0
  200. clm/layer2/lineage.py +322 -0
  201. clm/layer2/mcard_fs.py +262 -0
  202. clm/layer2/merkle.py +11 -0
  203. clm/layer2/mime.py +244 -0
  204. clm/layer2/mime_detector.py +245 -0
  205. clm/layer2/private_collection.py +118 -0
  206. clm/layer2/shared_store.py +11 -0
  207. clm/layer2/storage/__init__.py +190 -0
  208. clm/layer2/storage/db_bitemporal.py +141 -0
  209. clm/layer2/storage/db_driver.py +331 -0
  210. clm/layer2/storage/db_hyperlinks.py +214 -0
  211. clm/layer2/storage/db_pool.py +139 -0
  212. clm/layer2/storage/db_repository.py +229 -0
  213. clm/layer2/storage/savepoint.py +78 -0
  214. clm/layer2/storage/sqlite_wal.py +74 -0
  215. clm/layer2/tridb.py +245 -0
  216. clm/layer2/vcard_receipt.py +71 -0
  217. clm/layer2/vfs.py +17 -0
  218. clm/layer3/__init__.py +141 -0
  219. clm/layer3/agency/__init__.py +19 -0
  220. clm/layer3/agency/config.py +220 -0
  221. clm/layer3/agency/providers/__init__.py +15 -0
  222. clm/layer3/agency/providers/base.py +91 -0
  223. clm/layer3/agency/providers/mlc_llm.py +169 -0
  224. clm/layer3/agency/providers/ollama.py +203 -0
  225. clm/layer3/agency/router.py +71 -0
  226. clm/layer3/agency/runtime.py +524 -0
  227. clm/layer3/gateway.py +353 -0
  228. clm/layer3/open_interpreter_adapter.py +159 -0
  229. clm/layer3/rag/__init__.py +70 -0
  230. clm/layer3/rag/cli.py +228 -0
  231. clm/layer3/rag/config.py +153 -0
  232. clm/layer3/rag/embeddings/__init__.py +16 -0
  233. clm/layer3/rag/embeddings/base.py +79 -0
  234. clm/layer3/rag/embeddings/ollama.py +211 -0
  235. clm/layer3/rag/embeddings/vision.py +290 -0
  236. clm/layer3/rag/engine.py +276 -0
  237. clm/layer3/rag/graph/__init__.py +22 -0
  238. clm/layer3/rag/graph/community.py +198 -0
  239. clm/layer3/rag/graph/engine.py +433 -0
  240. clm/layer3/rag/graph/extractor.py +255 -0
  241. clm/layer3/rag/graph/schema.py +115 -0
  242. clm/layer3/rag/graph/store.py +641 -0
  243. clm/layer3/rag/indexer.py +333 -0
  244. clm/layer3/rag/llm_providers.json +52 -0
  245. clm/layer3/rag/semantic_versioning.py +313 -0
  246. clm/layer3/rag/vector/__init__.py +28 -0
  247. clm/layer3/rag/vector/handle_vector_store.py +749 -0
  248. clm/layer3/rag/vector/schema.py +151 -0
  249. clm/layer3/rag/vector/store.py +632 -0
  250. clm/layer3/reticulum/__init__.py +55 -0
  251. clm/layer3/reticulum/bridge.py +74 -0
  252. clm/layer3/reticulum/daemon.py +88 -0
  253. clm/layer3/reticulum/discovery.py +164 -0
  254. clm/layer3/reticulum/identity.py +107 -0
  255. clm/layer3/reticulum/media.py +148 -0
  256. clm/layer3/reticulum/transport.py +133 -0
  257. clm/layer3/satori/__init__.py +209 -0
  258. clm/layer3/satori/alpha_conversion.py +168 -0
  259. clm/layer3/satori/beta_reduction.py +359 -0
  260. clm/layer3/satori/continuation.py +38 -0
  261. clm/layer3/satori/eta_conversion.py +205 -0
  262. clm/layer3/satori/free_variables.py +163 -0
  263. clm/layer3/satori/io_effects.py +370 -0
  264. clm/layer3/satori/lambda_runtime.py +726 -0
  265. clm/layer3/satori/lambda_term.py +230 -0
  266. clm/layer3/satori/sheaf_audit.py +261 -0
  267. clm/layer3/satori/speech_act.py +279 -0
  268. clm/layer4/README.md +9 -0
  269. clm/layer4/__init__.py +67 -0
  270. clm/layer4/_sovereign_executor.py +580 -0
  271. clm/layer4/action_commands.py +719 -0
  272. clm/layer4/api.py +236 -0
  273. clm/layer4/bootstrap/__init__.py +13 -0
  274. clm/layer4/bootstrap/bootstrapper.py +118 -0
  275. clm/layer4/bootstrap/cleanup.py +44 -0
  276. clm/layer4/bootstrap/clm_hooks.py +49 -0
  277. clm/layer4/bootstrap/genesis.py +77 -0
  278. clm/layer4/bootstrap/injection.py +125 -0
  279. clm/layer4/cli.py +549 -0
  280. clm/layer4/collection_manager.py +553 -0
  281. clm/layer4/commands/__init__.py +81 -0
  282. clm/layer4/commands/action_cmd.py +42 -0
  283. clm/layer4/commands/check_cmd.py +82 -0
  284. clm/layer4/commands/envelope.py +73 -0
  285. clm/layer4/commands/evaluate_cmd.py +224 -0
  286. clm/layer4/commands/mcard_cmd.py +24 -0
  287. clm/layer4/commands/petri_cmd.py +92 -0
  288. clm/layer4/commands/tridb_cmd.py +319 -0
  289. clm/layer4/commands/version_cmd.py +27 -0
  290. clm/layer4/diff_commands.py +167 -0
  291. clm/layer4/eoa_sponsor.py +232 -0
  292. clm/layer4/events.py +69 -0
  293. clm/layer4/flux_gateway.py +133 -0
  294. clm/layer4/gateway.py +91 -0
  295. clm/layer4/loader.py +307 -0
  296. clm/layer4/mcard_commands.py +166 -0
  297. clm/layer4/membrane.py +273 -0
  298. clm/layer4/navigation.py +50 -0
  299. clm/layer4/protocol/protocol_entry_python.py +108 -0
  300. clm/layer4/protocol/python_sync.py +126 -0
  301. clm/layer4/runner.py +150 -0
  302. clm/layer4/satori.py +262 -0
  303. clm/layer4/server/python_sync_client.py +150 -0
  304. clm/layer4/server/python_sync_server.py +139 -0
  305. clm/layer4/server/service_api.py +315 -0
  306. clm/layer4/server/start_servers.py +107 -0
  307. clm/layer4/server/stop_servers.py +24 -0
  308. clm/layer4/simulated_optimization_fix.py +0 -0
  309. clm/layer4/storage.py +463 -0
  310. clm/layer4/tridb_admin.py +268 -0
  311. clm/layer4/websocket/test_ws_client.py +104 -0
  312. clm/layer4/websocket/test_ws_server.py +138 -0
  313. clm/layer4/websocket/ws_server_local.py +160 -0
  314. clm/layer5/__init__.py +51 -0
  315. clm/layer5/async_batch_writer.py +133 -0
  316. clm/layer5/learning.py +276 -0
  317. clm/layer5/telemetry_hook.py +107 -0
  318. clm/layer5/type_lattice.py +238 -0
  319. clm/py.typed +0 -0
  320. clm/schemas/__init__.py +652 -0
  321. clm/schemas/abstract_face_schema.yaml +77 -0
  322. clm/schemas/cel_schema.yaml +416 -0
  323. clm/schemas/lifecycle/__init__.py +45 -0
  324. clm/schemas/lifecycle/models.py +123 -0
  325. clm/schemas/lifecycle/service.py +364 -0
  326. clm/schemas/mcard_schema.sql +192 -0
  327. clm/schemas/mcard_vector_schema.sql +283 -0
  328. clm/schemas/mlp-context-v1.json +91 -0
  329. clm_kernel-0.0.0.dist-info/METADATA +121 -0
  330. clm_kernel-0.0.0.dist-info/RECORD +333 -0
  331. clm_kernel-0.0.0.dist-info/WHEEL +4 -0
  332. clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
  333. clm_kernel-0.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,307 @@
1
+ import hashlib
2
+ import logging
3
+ import os
4
+ import struct
5
+
6
+ from clm.layer0.model.hash.enums import HashAlgorithm
7
+
8
+ # Set up basic logging for the module
9
+ logger = logging.getLogger(__name__)
10
+ logger.setLevel(logging.DEBUG) # Set to DEBUG to capture detailed messages
11
+
12
+
13
+ # ── Zero-dependency pure Python BLAKE3 engine (INV-02 fallback) ─────────
14
+ _B3_IV = (
15
+ 0x6A09E667,
16
+ 0xBB67AE85,
17
+ 0x3C6EF372,
18
+ 0xA54FF53A,
19
+ 0x510E527F,
20
+ 0x9B05688C,
21
+ 0x1F83D9AB,
22
+ 0x5BE0CD19,
23
+ )
24
+ _B3_MSG_PERM = (2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8)
25
+ _B3_CHUNK_START, _B3_CHUNK_END, _B3_PARENT, _B3_ROOT = 1, 2, 4, 8
26
+
27
+
28
+ def _b3_rotr(w: int, r: int) -> int:
29
+ return ((w >> r) | (w << (32 - r))) & 0xFFFFFFFF
30
+
31
+
32
+ def _b3_g(s: list, a: int, b: int, c: int, d: int, mx: int, my: int) -> None:
33
+ s[a] = (s[a] + s[b] + mx) & 0xFFFFFFFF
34
+ s[d] = _b3_rotr(s[d] ^ s[a], 16)
35
+ s[c] = (s[c] + s[d]) & 0xFFFFFFFF
36
+ s[b] = _b3_rotr(s[b] ^ s[c], 12)
37
+ s[a] = (s[a] + s[b] + my) & 0xFFFFFFFF
38
+ s[d] = _b3_rotr(s[d] ^ s[a], 8)
39
+ s[c] = (s[c] + s[d]) & 0xFFFFFFFF
40
+ s[b] = _b3_rotr(s[b] ^ s[c], 7)
41
+
42
+
43
+ def _b3_round(s: list, m: list) -> None:
44
+ _b3_g(s, 0, 4, 8, 12, m[0], m[1])
45
+ _b3_g(s, 1, 5, 9, 13, m[2], m[3])
46
+ _b3_g(s, 2, 6, 10, 14, m[4], m[5])
47
+ _b3_g(s, 3, 7, 11, 15, m[6], m[7])
48
+ _b3_g(s, 0, 5, 10, 15, m[8], m[9])
49
+ _b3_g(s, 1, 6, 11, 12, m[10], m[11])
50
+ _b3_g(s, 2, 7, 8, 13, m[12], m[13])
51
+ _b3_g(s, 3, 4, 9, 14, m[14], m[15])
52
+
53
+
54
+ def _b3_compress(
55
+ cv: list, block_words: list, block_len: int, counter: int, flags: int
56
+ ) -> list:
57
+ s = (
58
+ list(cv)
59
+ + list(_B3_IV[:4])
60
+ + [counter & 0xFFFFFFFF, (counter >> 32) & 0xFFFFFFFF, block_len, flags]
61
+ )
62
+ m = list(block_words)
63
+ for r in range(7):
64
+ _b3_round(s, m)
65
+ if r < 6:
66
+ m = [m[_B3_MSG_PERM[i]] for i in range(16)]
67
+ out = [0] * 16
68
+ for i in range(8):
69
+ out[i] = (s[i] ^ s[i + 8]) & 0xFFFFFFFF
70
+ out[i + 8] = (s[i + 8] ^ cv[i]) & 0xFFFFFFFF
71
+ return out
72
+
73
+
74
+ def _b3_words_from_bytes(b: bytearray) -> list:
75
+ words = [0] * 16
76
+ for i in range(min(16, (len(b) + 3) // 4)):
77
+ chunk = b[i * 4 : (i + 1) * 4]
78
+ words[i] = int.from_bytes(chunk, "little")
79
+ return words
80
+
81
+
82
+ def _b3_words_to_hex(words: list) -> str:
83
+ return "".join(struct.pack("<I", w).hex() for w in words)
84
+
85
+
86
+ class _B3ChunkState:
87
+ def __init__(self, counter: int, flags: int = 0):
88
+ self.cv = list(_B3_IV)
89
+ self.counter = counter
90
+ self.flags = flags
91
+ self.block = bytearray()
92
+ self.blocks_compressed = 0
93
+
94
+ def start_flag(self) -> int:
95
+ return _B3_CHUNK_START if self.blocks_compressed == 0 else 0
96
+
97
+ def update(self, data: bytes) -> None:
98
+ offset = 0
99
+ while offset < len(data):
100
+ if len(self.block) == 64:
101
+ words = _b3_words_from_bytes(self.block)
102
+ out = _b3_compress(
103
+ self.cv, words, 64, self.counter, self.flags | self.start_flag()
104
+ )
105
+ self.cv = out[:8]
106
+ self.blocks_compressed += 1
107
+ self.block = bytearray()
108
+ take = min(64 - len(self.block), len(data) - offset)
109
+ self.block.extend(data[offset : offset + take])
110
+ offset += take
111
+
112
+ def output(self, is_root: bool = False) -> list:
113
+ words = _b3_words_from_bytes(self.block)
114
+ flags = self.flags | self.start_flag() | _B3_CHUNK_END
115
+ if is_root:
116
+ flags |= _B3_ROOT
117
+ return _b3_compress(self.cv, words, len(self.block), self.counter, flags)
118
+
119
+
120
+ def _b3_parent_output(left_cv: list, right_cv: list, is_root: bool = False) -> list:
121
+ block_words = left_cv[:8] + right_cv[:8]
122
+ flags = _B3_PARENT
123
+ if is_root:
124
+ flags |= _B3_ROOT
125
+ return _b3_compress(_B3_IV, block_words, 64, 0, flags)
126
+
127
+
128
+ def pure_python_blake3(data: bytes) -> str:
129
+ """Zero-dependency pure Python BLAKE3 hash computation."""
130
+ if isinstance(data, str):
131
+ data = data.encode("utf-8")
132
+ if len(data) <= 1024:
133
+ c = _B3ChunkState(0, 0)
134
+ c.update(data)
135
+ return _b3_words_to_hex(c.output(True)[:8])
136
+
137
+ stack = []
138
+ chunk_counter = 0
139
+ offset = 0
140
+ while offset + 1024 < len(data):
141
+ chunk_bytes = data[offset : offset + 1024]
142
+ c = _B3ChunkState(chunk_counter, 0)
143
+ c.update(chunk_bytes)
144
+ chunk_cv = c.output(False)[:8]
145
+ chunk_counter += 1
146
+ offset += 1024
147
+
148
+ total = chunk_counter
149
+ while (total & 1) == 0:
150
+ left_cv = stack.pop()
151
+ chunk_cv = _b3_parent_output(left_cv, chunk_cv, False)[:8]
152
+ total >>= 1
153
+ stack.append(chunk_cv)
154
+
155
+ final = _B3ChunkState(chunk_counter, 0)
156
+ final.update(data[offset:])
157
+ out = final.output(len(stack) == 0)[:8]
158
+ if len(stack) == 0:
159
+ return _b3_words_to_hex(out)
160
+
161
+ while stack:
162
+ left = stack.pop()
163
+ is_root = len(stack) == 0
164
+ out = _b3_parent_output(left, out, is_root)[:8]
165
+ return _b3_words_to_hex(out)
166
+
167
+
168
+ class HashValidator:
169
+ """Validates and computes hash values for content."""
170
+
171
+ def __init__(
172
+ self,
173
+ content: str | bytes,
174
+ hash_algorithm: str | HashAlgorithm = HashAlgorithm.DEFAULT,
175
+ ):
176
+ self.content = content
177
+ self.hash_algorithm = hash_algorithm
178
+ self.hash_value = self.compute_hash(content, hash_algorithm)
179
+
180
+ @staticmethod
181
+ def compute_hash(
182
+ content: str | bytes,
183
+ hash_algorithm: str | HashAlgorithm = HashAlgorithm.DEFAULT,
184
+ ) -> str:
185
+ """Compute hash for given content using specified hash function"""
186
+ content_to_hash = (
187
+ content if isinstance(content, bytes) else content.encode("utf-8")
188
+ ) # Explicit encoding
189
+
190
+ # Handle HashAlgorithm conversion
191
+ if isinstance(hash_algorithm, str):
192
+ try:
193
+ hash_algorithm = HashAlgorithm(hash_algorithm.lower())
194
+ except ValueError as e:
195
+ raise ValueError(
196
+ f"'{hash_algorithm}' is not a valid HashAlgorithm"
197
+ ) from e
198
+
199
+ if hash_algorithm == HashAlgorithm.CUSTOM:
200
+ raise ValueError(
201
+ "Custom hash function must be provided when using CUSTOM algorithm"
202
+ )
203
+
204
+ if hash_algorithm == HashAlgorithm.BLAKE3:
205
+ try:
206
+ import blake3
207
+
208
+ return blake3.blake3(content_to_hash).hexdigest()
209
+ except ImportError:
210
+ logger.warning(
211
+ "Py-blake3 not found. Attempting OS fallback for BLAKE3."
212
+ )
213
+ candidates = [
214
+ os.environ.get("CLM_RUST_CORE_BIN"),
215
+ os.path.join(
216
+ os.path.dirname(__file__),
217
+ "../../../clm/kernel/clm_rust_core/target/release/clm_rust_core",
218
+ ),
219
+ os.path.join(
220
+ os.path.dirname(__file__),
221
+ "../../../clm/kernel/clm_rust_core/target/debug/clm_rust_core",
222
+ ),
223
+ "clm_rust_core",
224
+ "b3sum",
225
+ ]
226
+ if os.environ.get("CLM_ZERO_FS", "").lower() == "true":
227
+ return pure_python_blake3(content_to_hash)
228
+
229
+ for bin_path in candidates:
230
+ if not bin_path:
231
+ continue
232
+ try:
233
+ import subprocess
234
+ # Assuming standard CLI argument structure for the tool
235
+ cmd = (
236
+ [bin_path, "hash"]
237
+ if "clm_rust_core" in bin_path
238
+ else [bin_path, "--no-names"]
239
+ )
240
+ res = subprocess.run(
241
+ cmd,
242
+ input=content_to_hash,
243
+ capture_output=True,
244
+ check=True,
245
+ )
246
+ out = res.stdout.decode("utf-8", errors="replace").strip()
247
+ if out:
248
+ # Attempt to extract the hash value cleanly
249
+ return out.split()[0]
250
+ except Exception as e:
251
+ logger.debug(f"BLAKE3 fallback failed with {bin_path}: {e}")
252
+
253
+ # Fallback to pure Python if all external methods fail
254
+ logger.info("Falling back to pure Python implementation for BLAKE3.")
255
+ return pure_python_blake3(content_to_hash)
256
+
257
+ hash_functions = {
258
+ HashAlgorithm.MD5: hashlib.md5,
259
+ HashAlgorithm.SHA1: hashlib.sha1,
260
+ HashAlgorithm.SHA224: hashlib.sha224,
261
+ HashAlgorithm.SHA256: hashlib.sha256,
262
+ HashAlgorithm.SHA384: hashlib.sha384,
263
+ HashAlgorithm.SHA512: hashlib.sha512,
264
+ }
265
+
266
+ if hash_algorithm not in hash_functions:
267
+ raise ValueError(f"'{hash_algorithm}' is not a valid HashAlgorithm")
268
+
269
+ return hash_functions[hash_algorithm](content_to_hash).hexdigest()
270
+
271
+ def validate(self, expected_hash: str = None):
272
+ """Validate that the stored hash matches the expected hash"""
273
+ if expected_hash is not None:
274
+ self.hash_value = expected_hash
275
+
276
+ target = self.hash_value
277
+ algo = self.hash_algorithm
278
+
279
+ # Attempt to parse combined hash format (prefix:bare)
280
+ if isinstance(target, str) and ":" in target:
281
+ try:
282
+ prefix, bare = target.split(":", 1)
283
+ # Assuming HashAlgorithm.from_string exists and works if prefix is valid
284
+ if hasattr(HashAlgorithm, "from_string"):
285
+ algo = HashAlgorithm.from_string(prefix)
286
+ target = bare
287
+ else:
288
+ logger.warning(
289
+ "HashAlgorithm.from_string not available. Using default parsing."
290
+ )
291
+ except ValueError:
292
+ pass # Keep original algo/target if parsing fails
293
+
294
+ calculated_hash = self.compute_hash(self.content, algo)
295
+ logger.debug(
296
+ f"Validation Check: expected '{self.hash_value}', calculated '{calculated_hash}'"
297
+ )
298
+ logger.debug(
299
+ f"Hash calculation details: content='{self.content}', hash_function='{algo}'"
300
+ )
301
+ if calculated_hash != target:
302
+ raise ValueError(
303
+ f"Hash mismatch: expected '{self.hash_value}', got '{calculated_hash}'"
304
+ )
305
+
306
+ def __str__(self):
307
+ return self.hash_value
@@ -0,0 +1,275 @@
1
+ """
2
+ Content type detection and validation service.
3
+
4
+ This module provides a unified interface for content type detection and validation
5
+ using a modular architecture with separate concerns for detection strategies,
6
+ validation, and content analysis utilities.
7
+ """
8
+
9
+ import json
10
+ import logging
11
+
12
+ try:
13
+ from defusedxml.ElementTree import fromstring as safe_fromstring
14
+ except ImportError:
15
+ from xml.etree.ElementTree import fromstring as safe_fromstring # nosec B314
16
+
17
+ # Import modular components
18
+ from .validators.validation_registry import ValidationError, validation_registry
19
+
20
+ try:
21
+ import mcard_core_py # type: ignore
22
+ except ImportError:
23
+ LOGGER = logging.getLogger(__name__)
24
+ LOGGER.warning(
25
+ "mcard_core_py native extension not found, functionality may be degraded."
26
+ )
27
+
28
+ # Module logger
29
+ LOGGER = logging.getLogger(__name__)
30
+
31
+ # Re-export ValidationError for backward compatibility
32
+ __all__ = ["ContentTypeInterpreter", "ValidationError", "get_interpreter"]
33
+
34
+ # Module-level singleton instance (lazy)
35
+ _default_interpreter: "ContentTypeInterpreter | None" = None
36
+
37
+
38
+ def get_interpreter() -> "ContentTypeInterpreter":
39
+ """Return the module-level ContentTypeInterpreter singleton."""
40
+ global _default_interpreter
41
+ if _default_interpreter is None:
42
+ _default_interpreter = ContentTypeInterpreter()
43
+ return _default_interpreter
44
+
45
+
46
+ class ContentTypeInterpreter:
47
+ """
48
+ Unified service for content type detection and validation.
49
+
50
+ This consolidated class uses modular components internally while maintaining
51
+ the same external API for backward compatibility.
52
+ """
53
+
54
+ # ─── Load shared MIME data from external JSON ─────────────────────────
55
+ # The canonical data lives in mime_extensions.json at the repo root.
56
+ # Both Python and TypeScript load from it, keeping them in sync.
57
+ @staticmethod
58
+ def _load_shared_mime_data() -> tuple[frozenset, dict]:
59
+ """Load MIME data from the shared JSON file.
60
+
61
+ Returns a (text_mime_types frozenset, mime_to_extension dict) tuple.
62
+ Falls back to minimal built-in defaults if the file is not found.
63
+ """
64
+ from pathlib import Path
65
+
66
+ # Walk up from this file to find mime_extensions.json at repo root
67
+ search = Path(__file__).resolve().parent
68
+ for _ in range(5):
69
+ candidate = search / "mime_extensions.json"
70
+ if candidate.exists():
71
+ break
72
+ search = search.parent
73
+ else:
74
+ candidate = None
75
+
76
+ if candidate and candidate.exists():
77
+ try:
78
+ with open(candidate, encoding="utf-8") as f:
79
+ data = json.load(f)
80
+ text_types = frozenset(data.get("textMimeTypes", []))
81
+ ext_map = dict(data.get("mimeToExtension", {}))
82
+ return text_types, ext_map
83
+ except Exception as e:
84
+ LOGGER.warning(
85
+ "Failed to load mime_extensions.json: %s — using defaults", e
86
+ )
87
+
88
+ # Minimal fallback if JSON file is missing
89
+ return frozenset({"text/plain", "application/json"}), {
90
+ "text/plain": "txt",
91
+ "application/json": "json",
92
+ }
93
+
94
+ # Populated from the shared JSON (or fallback)
95
+ _text_types, _ext_map = _load_shared_mime_data.__func__() # type: ignore[attr-defined]
96
+ TEXT_MIME_TYPES: frozenset = _text_types
97
+ _MIME_TO_EXTENSION: dict = _ext_map
98
+
99
+ @classmethod
100
+ def register_extension(cls, mime_type: str, extension: str) -> None:
101
+ """Dynamically register a new MIME-to-extension mapping.
102
+
103
+ Accepts extensions with or without a leading dot (e.g., '.custom' or 'custom').
104
+ The stored value will always be without a leading dot, consistent with the existing mapping.
105
+ """
106
+ if extension.startswith("."):
107
+ extension = extension[1:]
108
+ cls._MIME_TO_EXTENSION[mime_type] = extension
109
+
110
+ @classmethod
111
+ def register_extensions(cls, mapping: dict) -> None:
112
+ """Dynamically register a batch of MIME-to-extension mappings."""
113
+ for mime, ext in mapping.items():
114
+ cls.register_extension(mime, ext)
115
+
116
+ def __init__(self) -> None:
117
+ """
118
+ Initialize the interpreter.
119
+ """
120
+ pass
121
+
122
+ @staticmethod
123
+ def _decode_utf8(data: bytes) -> str:
124
+ """Decode bytes to UTF-8 string using a consistent policy."""
125
+ return data.decode("utf-8", errors="replace")
126
+
127
+ @staticmethod
128
+ def detect_content_type(
129
+ content: str | bytes, file_extension: str | None = None
130
+ ) -> tuple[str, str]:
131
+ """
132
+ Detect content type and suggested extension using Rust Core.
133
+ """
134
+ if isinstance(content, str):
135
+ content = content.encode("utf-8")
136
+ return mcard_core_py.detect_content_type(content, file_extension) # type: ignore
137
+
138
+ def detect_content_type_with_strategy(
139
+ self, content: str | bytes, file_extension: str | None = None
140
+ ) -> tuple[str, str]:
141
+ """
142
+ Detect content type using the configured strategy.
143
+
144
+ Args:
145
+ content: The content to analyze
146
+ file_extension: Optional file extension hint
147
+
148
+ Returns:
149
+ Tuple of (mime_type, extension)
150
+ """
151
+ return self.detect_content_type(content, file_extension)
152
+
153
+ def validate_content(self, content: str | bytes) -> None:
154
+ """
155
+ Validate content based on its detected type.
156
+
157
+ This method now delegates to the validation registry for better modularity.
158
+
159
+ Args:
160
+ content: The content to validate
161
+
162
+ Raises:
163
+ ValidationError: If content is invalid
164
+ """
165
+ mime_type, _ = self.detect_content_type(content)
166
+ validation_registry.validate(content, mime_type)
167
+
168
+ @staticmethod
169
+ def validate_content_static(content: str | bytes) -> None:
170
+ """
171
+ Static method for validating content (backward compatibility).
172
+
173
+ Args:
174
+ content: The content to validate
175
+
176
+ Raises:
177
+ ValidationError: If content is invalid
178
+ """
179
+ # Use default strategy for static method
180
+ mime_type, _ = ContentTypeInterpreter.detect_content_type(content)
181
+ validation_registry.validate(content, mime_type)
182
+
183
+ @staticmethod
184
+ def is_binary_content(content: str | bytes) -> bool:
185
+ """
186
+ Determine if content should be treated as binary using the Rust Core.
187
+ """
188
+ if isinstance(content, str):
189
+ content = content.encode("utf-8")
190
+ return mcard_core_py.is_binary_content(content) # type: ignore
191
+
192
+ @staticmethod
193
+ def is_xml_content(content: str | bytes) -> bool:
194
+ """Check if content is valid XML."""
195
+ # Delegate to XMLDetector's is_valid_xml method
196
+ try:
197
+ if isinstance(content, str):
198
+ content = content.encode("utf-8")
199
+
200
+ # Try to parse the XML without requiring XML declaration
201
+ safe_fromstring(content) # nosec B314
202
+ LOGGER.debug("Valid XML content detected.")
203
+ return True
204
+ except Exception as e:
205
+ LOGGER.debug("Invalid XML content detected: %s", str(e))
206
+ return False
207
+
208
+ @staticmethod
209
+ def is_svg_content(content: str | bytes) -> bool:
210
+ """Check if content is SVG."""
211
+ if isinstance(content, bytes):
212
+ content = content.decode("utf-8", errors="ignore")
213
+
214
+ # First check if it's valid XML
215
+ if not ContentTypeInterpreter.is_xml_content(content):
216
+ return False
217
+
218
+ try:
219
+ # Parse XML and check for SVG namespace
220
+ tree = safe_fromstring(content) # nosec B314
221
+ return (
222
+ tree.tag == "svg"
223
+ or tree.tag.endswith("}svg")
224
+ or any(
225
+ attr.endswith("xmlns") and "svg" in value
226
+ for attr, value in tree.attrib.items()
227
+ )
228
+ )
229
+ except Exception:
230
+ return False
231
+
232
+ @staticmethod
233
+ def is_mermaid_content(content: str) -> bool:
234
+ """Check if content is Mermaid diagram."""
235
+ content = content.strip().lower()
236
+ mermaid_keywords = [
237
+ "graph ",
238
+ "sequencediagram",
239
+ "classDiagram",
240
+ "stateDiagram",
241
+ "erDiagram",
242
+ "gantt",
243
+ "pie",
244
+ "flowchart",
245
+ "journey",
246
+ ]
247
+ return any(content.startswith(keyword.lower()) for keyword in mermaid_keywords)
248
+
249
+ @staticmethod
250
+ def is_diagram_content(content: str) -> bool:
251
+ """Check if content is a diagram format."""
252
+ content = content.strip().lower()
253
+ # Check for PlantUML
254
+ if content.startswith("@startuml") and content.endswith("@enduml"):
255
+ return True
256
+ # Check for Graphviz
257
+ if content.startswith(("digraph", "graph", "strict")):
258
+ return True
259
+ # Check for Mermaid
260
+ return ContentTypeInterpreter.is_mermaid_content(content)
261
+
262
+ @staticmethod
263
+ def get_extension(mime_type: str) -> str:
264
+ """Get file extension from MIME type using Rust Core."""
265
+ ext = mcard_core_py.get_extension(mime_type)
266
+ if not ext:
267
+ ext = ContentTypeInterpreter.get_default_extension(mime_type)
268
+ return ext if ext is not None else ""
269
+
270
+ @staticmethod
271
+ def get_default_extension(mime_type: str) -> str:
272
+ """
273
+ Return the default file extension for a given MIME type.
274
+ """
275
+ return ContentTypeInterpreter._MIME_TO_EXTENSION.get(mime_type, "")
@@ -0,0 +1,45 @@
1
+ """Pagination support for MCard collections."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Generic, TypeVar
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ @dataclass
10
+ class Page(Generic[T]):
11
+ """A page of search results."""
12
+
13
+ items: list[T]
14
+ page_number: int
15
+ page_size: int
16
+ total_items: int
17
+ total_pages: int
18
+ has_next: bool
19
+ has_previous: bool
20
+
21
+ @classmethod
22
+ def create(
23
+ cls, items: list, total_items: int, page_number: int, page_size: int
24
+ ) -> "Page":
25
+ """Factory that computes has_next, has_previous, and total_pages automatically."""
26
+ total_pages = (total_items + page_size - 1) // page_size if page_size > 0 else 0
27
+ return cls(
28
+ items=items,
29
+ total_items=total_items,
30
+ page_number=page_number,
31
+ page_size=page_size,
32
+ total_pages=total_pages,
33
+ has_next=total_items > page_number * page_size,
34
+ has_previous=page_number > 1,
35
+ )
36
+
37
+ @property
38
+ def next_page_number(self) -> int | None:
39
+ """Get the next page number, or None if this is the last page."""
40
+ return self.page_number + 1 if self.has_next else None
41
+
42
+ @property
43
+ def previous_page_number(self) -> int | None:
44
+ """Get the previous page number, or None if this is the first page."""
45
+ return self.page_number - 1 if self.has_previous else None