lodedb 1.3.2__tar.gz → 2.0.0__tar.gz

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 (234) hide show
  1. {lodedb-1.3.2 → lodedb-2.0.0}/Cargo.toml +4 -2
  2. lodedb-1.3.2/README.md → lodedb-2.0.0/PKG-INFO +138 -1
  3. lodedb-1.3.2/PKG-INFO → lodedb-2.0.0/README.md +81 -53
  4. lodedb-2.0.0/crates/lodedb-cloud-core/Cargo.toml +41 -0
  5. lodedb-2.0.0/crates/lodedb-cloud-core/src/artifact_store.rs +127 -0
  6. lodedb-2.0.0/crates/lodedb-cloud-core/src/blob_layout.rs +119 -0
  7. lodedb-2.0.0/crates/lodedb-cloud-core/src/client_ops.rs +621 -0
  8. lodedb-2.0.0/crates/lodedb-cloud-core/src/digest.rs +46 -0
  9. lodedb-2.0.0/crates/lodedb-cloud-core/src/error.rs +133 -0
  10. lodedb-2.0.0/crates/lodedb-cloud-core/src/generation_inventory.rs +502 -0
  11. lodedb-2.0.0/crates/lodedb-cloud-core/src/lib.rs +83 -0
  12. lodedb-2.0.0/crates/lodedb-cloud-core/src/local_artifact_store.rs +190 -0
  13. lodedb-2.0.0/crates/lodedb-cloud-core/src/managed.rs +440 -0
  14. lodedb-2.0.0/crates/lodedb-cloud-core/src/manifest_transfer.rs +245 -0
  15. lodedb-2.0.0/crates/lodedb-cloud-core/src/object_artifact_store.rs +495 -0
  16. lodedb-2.0.0/crates/lodedb-cloud-core/src/paths.rs +153 -0
  17. lodedb-2.0.0/crates/lodedb-cloud-core/src/snapshot_identity.rs +72 -0
  18. lodedb-2.0.0/crates/lodedb-cloud-core/src/status.rs +143 -0
  19. lodedb-2.0.0/crates/lodedb-cloud-core/src/store_target.rs +58 -0
  20. lodedb-2.0.0/crates/lodedb-cloud-core/src/sync_plan.rs +225 -0
  21. lodedb-2.0.0/crates/lodedb-cloud-core/src/sync_state.rs +212 -0
  22. lodedb-2.0.0/crates/lodedb-cloud-core/src/transfer_policy.rs +81 -0
  23. lodedb-2.0.0/crates/lodedb-cloud-core/src/verify.rs +160 -0
  24. lodedb-2.0.0/crates/lodedb-cloud-core/tests/artifact_store.rs +236 -0
  25. lodedb-2.0.0/crates/lodedb-cloud-core/tests/client_ops.rs +105 -0
  26. lodedb-2.0.0/crates/lodedb-cloud-core/tests/common/mod.rs +276 -0
  27. lodedb-2.0.0/crates/lodedb-cloud-core/tests/generation_inventory.rs +413 -0
  28. lodedb-2.0.0/crates/lodedb-cloud-core/tests/managed.rs +268 -0
  29. lodedb-2.0.0/crates/lodedb-cloud-core/tests/manifest_transfer.rs +317 -0
  30. lodedb-2.0.0/crates/lodedb-cloud-core/tests/object_artifact_store.rs +285 -0
  31. lodedb-2.0.0/crates/lodedb-cloud-core/tests/restore.rs +283 -0
  32. lodedb-2.0.0/crates/lodedb-cloud-core/tests/snapshot_identity.rs +67 -0
  33. lodedb-2.0.0/crates/lodedb-cloud-core/tests/store_target.rs +35 -0
  34. lodedb-2.0.0/crates/lodedb-cloud-core/tests/sync_ops.rs +663 -0
  35. lodedb-2.0.0/crates/lodedb-cloud-core/tests/sync_plan.rs +273 -0
  36. lodedb-2.0.0/crates/lodedb-cloud-core/tests/sync_state.rs +125 -0
  37. lodedb-2.0.0/crates/lodedb-cloud-core/tests/verify_status.rs +183 -0
  38. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/engine/mod.rs +161 -43
  39. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/commit_manifest.rs +47 -9
  40. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/mod.rs +1 -1
  41. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/util.rs +2 -2
  42. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/wal.rs +8 -8
  43. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/vector/turbovec.rs +103 -10
  44. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/version.rs +6 -2
  45. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/core_engine.rs +143 -2
  46. lodedb-2.0.0/crates/lodedb-core/tests/turbovec_adapter.rs +191 -0
  47. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-gpu/src/lib.rs +4 -4
  48. lodedb-2.0.0/crates/lodedb-graph/Cargo.toml +28 -0
  49. lodedb-2.0.0/crates/lodedb-graph/src/error.rs +61 -0
  50. lodedb-2.0.0/crates/lodedb-graph/src/graph.rs +809 -0
  51. lodedb-2.0.0/crates/lodedb-graph/src/index.rs +883 -0
  52. lodedb-2.0.0/crates/lodedb-graph/src/lib.rs +51 -0
  53. lodedb-2.0.0/crates/lodedb-graph/src/model.rs +231 -0
  54. lodedb-2.0.0/crates/lodedb-graph/src/search.rs +278 -0
  55. lodedb-2.0.0/crates/lodedb-graph/src/temporal.rs +126 -0
  56. lodedb-2.0.0/crates/lodedb-graph/src/topology.rs +1372 -0
  57. lodedb-2.0.0/crates/lodedb-graph/tests/temporal_graph.rs +608 -0
  58. {lodedb-1.3.2 → lodedb-2.0.0}/pyproject.toml +19 -2
  59. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/__init__.py +2 -2
  60. lodedb-2.0.0/src/lodedb/cloud/__init__.py +113 -0
  61. lodedb-2.0.0/src/lodedb/cloud/_agents_scaffold.py +204 -0
  62. lodedb-2.0.0/src/lodedb/cloud/_config.py +159 -0
  63. lodedb-2.0.0/src/lodedb/cloud/_env_file.py +120 -0
  64. lodedb-2.0.0/src/lodedb/cloud/_sealing.py +60 -0
  65. lodedb-2.0.0/src/lodedb/cloud/cli.py +1845 -0
  66. lodedb-2.0.0/src/lodedb/cloud/client.py +447 -0
  67. lodedb-2.0.0/src/lodedb/cloud/serving.py +972 -0
  68. lodedb-2.0.0/src/lodedb/cloud/transfer.py +1086 -0
  69. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/_atomic_io.py +2 -2
  70. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/_commit_manifest.py +6 -6
  71. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/_filelock.py +4 -4
  72. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/_lexical.py +3 -3
  73. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/_predicate.py +4 -4
  74. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/embedding_backends.py +63 -34
  75. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/gpu_turbovec.py +3 -3
  76. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/route_registry.py +1 -1
  77. lodedb-2.0.0/src/lodedb/graph/__init__.py +27 -0
  78. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/graph/_store.py +2 -2
  79. lodedb-2.0.0/src/lodedb/graph/temporal.py +362 -0
  80. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/__init__.py +5 -5
  81. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/backends.py +1 -1
  82. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/benchmark.py +2 -2
  83. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/cli.py +36 -2
  84. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/db.py +99 -25
  85. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/integrations/__init__.py +9 -9
  86. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/integrations/kotaemon.py +5 -5
  87. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/integrations/langchain.py +2 -2
  88. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/integrations/llama_index.py +1 -1
  89. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/integrations/llama_index_graph.py +3 -3
  90. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/integrations/privategpt.py +5 -5
  91. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/mcp_install.py +5 -5
  92. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/mcp_server.py +2 -2
  93. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/detect.py +4 -4
  94. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/report.py +2 -2
  95. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/runner.py +1 -1
  96. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/sources/__init__.py +4 -4
  97. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/sources/base.py +2 -2
  98. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/onnx_artifacts.py +3 -3
  99. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/segments.py +10 -10
  100. lodedb-2.0.0/third_party/turbovec/Cargo.lock +3095 -0
  101. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/Cargo.toml +1 -1
  102. lodedb-2.0.0/third_party/turbovec/turbovec-python/Cargo.toml +29 -0
  103. lodedb-2.0.0/third_party/turbovec/turbovec-python/src/cloud.rs +455 -0
  104. lodedb-2.0.0/third_party/turbovec/turbovec-python/src/graph.rs +510 -0
  105. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/src/lib.rs +8 -0
  106. lodedb-1.3.2/crates/lodedb-core/tests/turbovec_adapter.rs +0 -90
  107. lodedb-1.3.2/src/lodedb/graph/__init__.py +0 -13
  108. lodedb-1.3.2/third_party/turbovec/Cargo.lock +0 -1455
  109. lodedb-1.3.2/third_party/turbovec/turbovec-python/Cargo.toml +0 -21
  110. {lodedb-1.3.2 → lodedb-2.0.0}/LICENSE +0 -0
  111. {lodedb-1.3.2 → lodedb-2.0.0}/NOTICE +0 -0
  112. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/Cargo.toml +0 -0
  113. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/examples/filter_planner_bench.rs +0 -0
  114. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/examples/turbovec_adapter_smoke.rs +0 -0
  115. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/examples/write_generation_fixture.rs +0 -0
  116. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/examples/write_wal_fixture.rs +0 -0
  117. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/error.rs +0 -0
  118. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/filter/ast.rs +0 -0
  119. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/filter/doc_set.rs +0 -0
  120. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/filter/field_index.rs +0 -0
  121. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/filter/mod.rs +0 -0
  122. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/filter/predicate.rs +0 -0
  123. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/filter/resolve.rs +0 -0
  124. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/filter/validate.rs +0 -0
  125. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/lexical/bm25.rs +0 -0
  126. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/lexical/mod.rs +0 -0
  127. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/lexical/rrf.rs +0 -0
  128. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/lexical/tokenize.rs +0 -0
  129. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/lib.rs +0 -0
  130. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/legacy.rs +0 -0
  131. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/lexical_store.rs +0 -0
  132. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/lsn.rs +0 -0
  133. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/multivec_store.rs +0 -0
  134. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/state_journal.rs +0 -0
  135. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/text_store.rs +0 -0
  136. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/tvann_store.rs +0 -0
  137. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/tvim_delta.rs +0 -0
  138. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/storage/tvvf_store.rs +0 -0
  139. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/text/chunk.rs +0 -0
  140. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/text/hash.rs +0 -0
  141. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/text/mod.rs +0 -0
  142. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/types.rs +0 -0
  143. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/vector/ann.rs +0 -0
  144. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/vector/index.rs +0 -0
  145. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/vector/math.rs +0 -0
  146. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/vector/mod.rs +0 -0
  147. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/src/vector/stable_id.rs +0 -0
  148. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/ann_build_bench.rs +0 -0
  149. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/block_skip_layout.rs +0 -0
  150. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/filter_plan_fixtures.rs +0 -0
  151. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/identity_fixtures.rs +0 -0
  152. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/lexical_fixtures.rs +0 -0
  153. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/operator_observability.rs +0 -0
  154. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/predicate_fixtures.rs +0 -0
  155. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-core/tests/storage_fixtures.rs +0 -0
  156. {lodedb-1.3.2 → lodedb-2.0.0}/crates/lodedb-gpu/Cargo.toml +0 -0
  157. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/__main__.py +0 -0
  158. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/_native_core.py +0 -0
  159. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/config.py +0 -0
  160. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/__init__.py +0 -0
  161. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/core.py +0 -0
  162. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/native_adapter.py +0 -0
  163. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/route_profiles.py +0 -0
  164. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/runtime_policy.py +0 -0
  165. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/state_journal_store.py +0 -0
  166. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/turbovec_delta_store.py +0 -0
  167. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/engine/turbovec_index.py +0 -0
  168. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/graph/knowledge_graph.py +0 -0
  169. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/appender.py +0 -0
  170. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/checkpointer.py +0 -0
  171. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/collection.py +0 -0
  172. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/doctor.py +0 -0
  173. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/integrations/cognee.py +0 -0
  174. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/integrations/mem0.py +0 -0
  175. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/late_interaction.py +0 -0
  176. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/__init__.py +0 -0
  177. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/cli.py +0 -0
  178. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/plan.py +0 -0
  179. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/sources/langchain_inmemory.py +0 -0
  180. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/sources/llama_index_simple.py +0 -0
  181. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/sources/mem0_qdrant.py +0 -0
  182. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/migrate/sources/pgvector.py +0 -0
  183. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/presets.py +0 -0
  184. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/local/server.py +0 -0
  185. {lodedb-1.3.2 → lodedb-2.0.0}/src/lodedb/py.typed +0 -0
  186. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/Cargo.toml +0 -0
  187. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/README.md +0 -0
  188. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/build.rs +0 -0
  189. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/examples/dump_state.rs +0 -0
  190. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/examples/kernel_xtest.rs +0 -0
  191. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/codebook.rs +0 -0
  192. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/encode.rs +0 -0
  193. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/error.rs +0 -0
  194. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/id_map.rs +0 -0
  195. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/io.rs +0 -0
  196. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/lib.rs +0 -0
  197. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/maxsim.rs +0 -0
  198. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/pack.rs +0 -0
  199. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/rotation.rs +0 -0
  200. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/src/search.rs +0 -0
  201. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/codebook.rs +0 -0
  202. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/concurrent_search.rs +0 -0
  203. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/distortion.rs +0 -0
  204. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/encode.rs +0 -0
  205. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/encoded_rows.rs +0 -0
  206. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/filtering.rs +0 -0
  207. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/id_map.rs +0 -0
  208. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/input_validation.rs +0 -0
  209. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/io_versioning.rs +0 -0
  210. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/kernel_correctness.rs +0 -0
  211. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/lazy_init.rs +0 -0
  212. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/reconstruction.rs +0 -0
  213. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/rotation.rs +0 -0
  214. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/state_sequences.rs +0 -0
  215. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/swap_remove.rs +0 -0
  216. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec/tests/tqplus_calibration.rs +0 -0
  217. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/README.md +0 -0
  218. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/build.rs +0 -0
  219. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/python/turbovec/__init__.py +0 -0
  220. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/python/turbovec/_dedup.py +0 -0
  221. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/python/turbovec/_persist.py +0 -0
  222. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/python/turbovec/agno.py +0 -0
  223. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/python/turbovec/haystack.py +0 -0
  224. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/python/turbovec/langchain.py +0 -0
  225. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/python/turbovec/llama_index.py +0 -0
  226. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_agno.py +0 -0
  227. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_dedup.py +0 -0
  228. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_filtering.py +0 -0
  229. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_haystack.py +0 -0
  230. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_id_map.py +0 -0
  231. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_index.py +0 -0
  232. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_langchain.py +0 -0
  233. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_llama_index.py +0 -0
  234. {lodedb-1.3.2 → lodedb-2.0.0}/third_party/turbovec/turbovec-python/tests/test_security.py +0 -0
@@ -1,15 +1,17 @@
1
1
  [workspace]
2
2
  members = [
3
+ "crates/lodedb-cloud-core",
3
4
  "crates/lodedb-core",
4
5
  "crates/lodedb-ffi",
5
6
  "crates/lodedb-gpu",
7
+ "crates/lodedb-graph",
6
8
  ]
7
9
  resolver = "2"
8
10
 
9
11
  [workspace.package]
10
- version = "1.3.2"
12
+ version = "2.0.0"
11
13
  edition = "2021"
12
- rust-version = "1.70"
14
+ rust-version = "1.88"
13
15
  license = "Apache-2.0"
14
16
  repository = "https://github.com/Egoist-Machines/LodeDB"
15
17
 
@@ -1,3 +1,59 @@
1
+ Metadata-Version: 2.4
2
+ Name: lodedb
3
+ Version: 2.0.0
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: Programming Language :: Python :: 3 :: Only
7
+ Classifier: Topic :: Database :: Database Engines/Servers
8
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
9
+ Requires-Dist: numpy>=2.0.0,<3
10
+ Requires-Dist: typer>=0.12.0
11
+ Requires-Dist: pyyaml>=6.0.0
12
+ Requires-Dist: lodedb[embeddings,torch,image,mcp,langchain,llama-index,mem0] ; extra == 'all'
13
+ Requires-Dist: httpx>=0.27 ; extra == 'cloud'
14
+ Requires-Dist: pynacl>=1.5 ; extra == 'cloud'
15
+ Requires-Dist: cryptography>=47 ; extra == 'cloud-sealed'
16
+ Requires-Dist: cognee>=1.1.0,<2 ; python_full_version < '3.15' and extra == 'cognee'
17
+ Requires-Dist: pytest>=8.3.0 ; extra == 'dev'
18
+ Requires-Dist: ruff==0.15.17 ; extra == 'dev'
19
+ Requires-Dist: onnxruntime>=1.20.0,<2 ; extra == 'embeddings'
20
+ Requires-Dist: transformers>=4.40.0,<5 ; extra == 'embeddings'
21
+ Requires-Dist: cupy-cuda12x>=13.0.0 ; platform_system == 'Linux' and extra == 'gpu'
22
+ Requires-Dist: pillow>=10.0.0 ; extra == 'image'
23
+ Requires-Dist: lodedb[torch] ; extra == 'image'
24
+ Requires-Dist: langchain-core>=0.3.0 ; extra == 'langchain'
25
+ Requires-Dist: llama-index-core>=0.11.0 ; extra == 'llama-index'
26
+ Requires-Dist: mcp>=1.0.0 ; extra == 'mcp'
27
+ Requires-Dist: mem0ai>=2.0.0 ; extra == 'mem0'
28
+ Requires-Dist: optimum[exporters]>=1.23.0,<2.0.0 ; extra == 'onnx-export'
29
+ Requires-Dist: sentence-transformers>=3.0.0,<5 ; extra == 'torch'
30
+ Provides-Extra: all
31
+ Provides-Extra: cloud
32
+ Provides-Extra: cloud-sealed
33
+ Provides-Extra: cognee
34
+ Provides-Extra: dev
35
+ Provides-Extra: embeddings
36
+ Provides-Extra: gpu
37
+ Provides-Extra: image
38
+ Provides-Extra: langchain
39
+ Provides-Extra: llama-index
40
+ Provides-Extra: mcp
41
+ Provides-Extra: mem0
42
+ Provides-Extra: onnx-export
43
+ Provides-Extra: torch
44
+ License-File: LICENSE
45
+ License-File: NOTICE
46
+ Summary: Local-first, privacy-by-default embedded vector database. Your data never leaves your machine.
47
+ Keywords: vector-database,embeddings,local-first,privacy,semantic-search,rag,cuda,gpu-acceleration,turbovec
48
+ Author-email: Egoist Machines <oss@egoistmachines.com>
49
+ License-Expression: Apache-2.0
50
+ Requires-Python: >=3.11
51
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
52
+ Project-URL: Changelog, https://github.com/Egoist-Machines/LodeDB/blob/main/CHANGELOG.md
53
+ Project-URL: Homepage, https://github.com/Egoist-Machines/LodeDB
54
+ Project-URL: Issues, https://github.com/Egoist-Machines/LodeDB/issues
55
+ Project-URL: Repository, https://github.com/Egoist-Machines/LodeDB
56
+
1
57
  <h1 align="center">LodeDB</h1>
2
58
  <p align="center">🔥 the <b>fastest</b> and <i>most compact</i> embedded vector database in the world 🌍</p>
3
59
 
@@ -605,6 +661,86 @@ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste star
605
661
 
606
662
  </details>
607
663
 
664
+ ## Managed cloud (OreCloud)
665
+
666
+ LodeDB stores can live in [OreCloud](https://db.egoistmachines.com), the managed-cloud
667
+ companion: durable sync for local stores, hosted read serving, and a per-user memory
668
+ data plane for agent applications. The client ships as the `[cloud]` extra:
669
+
670
+ ```sh
671
+ pip install "lodedb[cloud]"
672
+
673
+ lodedb cloud login # one browser approval
674
+ lodedb cloud init ./my-store # link the directory to a managed remote
675
+ lodedb cloud keys ./my-store # list the directory's index keys
676
+ lodedb cloud sync ./my-store cloud <key> # mirror the local store to the cloud
677
+ ```
678
+
679
+ A cloud store opens through the same class as a local one, via the
680
+ `LodeDB.cloud` alternate constructor:
681
+
682
+ ```python
683
+ from lodedb import LodeDB
684
+
685
+ db = LodeDB("./notes") # local, as always
686
+ db = LodeDB.cloud("user-42") # managed cloud, same verbs
687
+ db.add("the quick brown fox") # embedded server-side
688
+ db.search("fox", k=5)
689
+ ```
690
+
691
+ A bare store id is enough. The org/environment half resolves from the
692
+ credential (`token=`, the `ORECLOUD_TOKEN` environment variable, or
693
+ `lodedb cloud login`); pass the full `"org/environment/store"` triple in
694
+ cross-environment scripts. For config-driven code, where one string field (an
695
+ env var, a YAML value) must express either a local path or a cloud store, the
696
+ plain constructor accepts the explicit URL form
697
+ `LodeDB("orecloud://org/environment/store")` and returns the same handle.
698
+ An application serving many end users should hold one `Client` and open
699
+ per-user handles from it (`Client().store(user_id)`); the credential resolves
700
+ once and the handles share one HTTP pool. `from lodedb.cloud import Client`
701
+ works as the import root.
702
+
703
+ Work locally with `lodedb` exactly as before. The client is first-party code
704
+ in this package (`lodedb.cloud` and the `lodedb cloud` CLI), and push/pull/
705
+ sync run on the same bundled native core as the engine. The extra installs
706
+ only its dependencies (`httpx`, `pynacl`). Everything cloud loads lazily, so
707
+ a plain `import lodedb` stays network-free.
708
+
709
+ ### Sealed stores
710
+
711
+ OreCloud can host a server-side encrypted store whose 32-byte wrapping key
712
+ remains with your application. Install the additional lazy crypto support,
713
+ keep that material outside source control, and keep a recovery copy: losing
714
+ it makes the store unreadable and the service never persists it.
715
+
716
+ ```sh
717
+ pip install "lodedb[cloud,cloud-sealed]"
718
+ ```
719
+
720
+ ```python
721
+ import secrets
722
+
723
+ from lodedb.cloud import Client
724
+
725
+ material = secrets.token_bytes(32)
726
+ client = Client()
727
+ client.create_store(
728
+ "user-42",
729
+ encrypted=True,
730
+ key_material=material,
731
+ mode="cloud_writer",
732
+ preset="minilm",
733
+ )
734
+ expires_at = client.unseal_store("user-42", material, ttl_seconds=900)
735
+ # Query or write while the grant is live, then remove it when finished.
736
+ client.reseal_store("user-42")
737
+ ```
738
+
739
+ `Client.rotate_store_key("user-42", fresh_material)` replaces the caller-held
740
+ material after the store is unsealed. A sealed data-plane read returns
741
+ `CloudError` with status 423 and a `store_sealed:` detail; applications decide
742
+ when to request an unseal grant.
743
+
608
744
  ## Concurrency & durability
609
745
 
610
746
  - **Single writer, many readers, per path.** One handle holds the path open for *writing* at
@@ -648,7 +784,7 @@ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste star
648
784
  read-your-writes against an append's returned LSN). On Windows the shared lock degrades to an
649
785
  exclusive hold, so appenders serialize there rather than coexisting. A record is a precomputed
650
786
  vector plus metadata (with an optional caption, e.g. for an image, retained only when the appender
651
- opts into `store_text` -- off by default, so no raw text reaches the WAL); with an embedder
787
+ opts into `store_text`; it is off by default, so no raw text reaches the WAL); with an embedder
652
788
  configured, the appender also ingests full text (chunked by the core, embedded in the binding
653
789
  layer, then logged as a post-embedding record) so text writes are multi-producer too, without a
654
790
  captured base generation. It requires WAL commit mode. Exposed as the native `CoreAppender`, over
@@ -723,3 +859,4 @@ Enterprise licensing and commercial support are available from
723
859
  PRs welcome; see [`CONTRIBUTING.md`](CONTRIBUTING.md). Report security issues **privately**
724
860
  per [`SECURITY.md`](SECURITY.md), not in public issues. Other bugs and requests go to the
725
861
  [issue tracker](https://github.com/Egoist-Machines/LodeDB/issues).
862
+
@@ -1,54 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: lodedb
3
- Version: 1.3.2
4
- Classifier: Development Status :: 4 - Beta
5
- Classifier: Intended Audience :: Developers
6
- Classifier: Programming Language :: Python :: 3 :: Only
7
- Classifier: Topic :: Database :: Database Engines/Servers
8
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
9
- Requires-Dist: numpy>=2.0.0,<3
10
- Requires-Dist: typer>=0.12.0
11
- Requires-Dist: pyyaml>=6.0.0
12
- Requires-Dist: lodedb[embeddings,torch,image,mcp,langchain,llama-index,mem0] ; extra == 'all'
13
- Requires-Dist: cognee>=1.1.0,<2 ; python_full_version < '3.15' and extra == 'cognee'
14
- Requires-Dist: pytest>=8.3.0 ; extra == 'dev'
15
- Requires-Dist: ruff==0.15.17 ; extra == 'dev'
16
- Requires-Dist: onnxruntime>=1.20.0,<2 ; extra == 'embeddings'
17
- Requires-Dist: transformers>=4.40.0,<5 ; extra == 'embeddings'
18
- Requires-Dist: cupy-cuda12x>=13.0.0 ; platform_system == 'Linux' and extra == 'gpu'
19
- Requires-Dist: pillow>=10.0.0 ; extra == 'image'
20
- Requires-Dist: lodedb[torch] ; extra == 'image'
21
- Requires-Dist: langchain-core>=0.3.0 ; extra == 'langchain'
22
- Requires-Dist: llama-index-core>=0.11.0 ; extra == 'llama-index'
23
- Requires-Dist: mcp>=1.0.0 ; extra == 'mcp'
24
- Requires-Dist: mem0ai>=2.0.0 ; extra == 'mem0'
25
- Requires-Dist: optimum[exporters]>=1.23.0,<2.0.0 ; extra == 'onnx-export'
26
- Requires-Dist: sentence-transformers>=3.0.0,<5 ; extra == 'torch'
27
- Provides-Extra: all
28
- Provides-Extra: cognee
29
- Provides-Extra: dev
30
- Provides-Extra: embeddings
31
- Provides-Extra: gpu
32
- Provides-Extra: image
33
- Provides-Extra: langchain
34
- Provides-Extra: llama-index
35
- Provides-Extra: mcp
36
- Provides-Extra: mem0
37
- Provides-Extra: onnx-export
38
- Provides-Extra: torch
39
- License-File: LICENSE
40
- License-File: NOTICE
41
- Summary: Local-first, privacy-by-default embedded vector database. Your data never leaves your machine.
42
- Keywords: vector-database,embeddings,local-first,privacy,semantic-search,rag,cuda,gpu-acceleration,turbovec
43
- Author-email: Egoist Machines <oss@egoistmachines.com>
44
- License-Expression: Apache-2.0
45
- Requires-Python: >=3.11
46
- Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
47
- Project-URL: Changelog, https://github.com/Egoist-Machines/LodeDB/blob/main/CHANGELOG.md
48
- Project-URL: Homepage, https://github.com/Egoist-Machines/LodeDB
49
- Project-URL: Issues, https://github.com/Egoist-Machines/LodeDB/issues
50
- Project-URL: Repository, https://github.com/Egoist-Machines/LodeDB
51
-
52
1
  <h1 align="center">LodeDB</h1>
53
2
  <p align="center">🔥 the <b>fastest</b> and <i>most compact</i> embedded vector database in the world 🌍</p>
54
3
 
@@ -656,6 +605,86 @@ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste star
656
605
 
657
606
  </details>
658
607
 
608
+ ## Managed cloud (OreCloud)
609
+
610
+ LodeDB stores can live in [OreCloud](https://db.egoistmachines.com), the managed-cloud
611
+ companion: durable sync for local stores, hosted read serving, and a per-user memory
612
+ data plane for agent applications. The client ships as the `[cloud]` extra:
613
+
614
+ ```sh
615
+ pip install "lodedb[cloud]"
616
+
617
+ lodedb cloud login # one browser approval
618
+ lodedb cloud init ./my-store # link the directory to a managed remote
619
+ lodedb cloud keys ./my-store # list the directory's index keys
620
+ lodedb cloud sync ./my-store cloud <key> # mirror the local store to the cloud
621
+ ```
622
+
623
+ A cloud store opens through the same class as a local one, via the
624
+ `LodeDB.cloud` alternate constructor:
625
+
626
+ ```python
627
+ from lodedb import LodeDB
628
+
629
+ db = LodeDB("./notes") # local, as always
630
+ db = LodeDB.cloud("user-42") # managed cloud, same verbs
631
+ db.add("the quick brown fox") # embedded server-side
632
+ db.search("fox", k=5)
633
+ ```
634
+
635
+ A bare store id is enough. The org/environment half resolves from the
636
+ credential (`token=`, the `ORECLOUD_TOKEN` environment variable, or
637
+ `lodedb cloud login`); pass the full `"org/environment/store"` triple in
638
+ cross-environment scripts. For config-driven code, where one string field (an
639
+ env var, a YAML value) must express either a local path or a cloud store, the
640
+ plain constructor accepts the explicit URL form
641
+ `LodeDB("orecloud://org/environment/store")` and returns the same handle.
642
+ An application serving many end users should hold one `Client` and open
643
+ per-user handles from it (`Client().store(user_id)`); the credential resolves
644
+ once and the handles share one HTTP pool. `from lodedb.cloud import Client`
645
+ works as the import root.
646
+
647
+ Work locally with `lodedb` exactly as before. The client is first-party code
648
+ in this package (`lodedb.cloud` and the `lodedb cloud` CLI), and push/pull/
649
+ sync run on the same bundled native core as the engine. The extra installs
650
+ only its dependencies (`httpx`, `pynacl`). Everything cloud loads lazily, so
651
+ a plain `import lodedb` stays network-free.
652
+
653
+ ### Sealed stores
654
+
655
+ OreCloud can host a server-side encrypted store whose 32-byte wrapping key
656
+ remains with your application. Install the additional lazy crypto support,
657
+ keep that material outside source control, and keep a recovery copy: losing
658
+ it makes the store unreadable and the service never persists it.
659
+
660
+ ```sh
661
+ pip install "lodedb[cloud,cloud-sealed]"
662
+ ```
663
+
664
+ ```python
665
+ import secrets
666
+
667
+ from lodedb.cloud import Client
668
+
669
+ material = secrets.token_bytes(32)
670
+ client = Client()
671
+ client.create_store(
672
+ "user-42",
673
+ encrypted=True,
674
+ key_material=material,
675
+ mode="cloud_writer",
676
+ preset="minilm",
677
+ )
678
+ expires_at = client.unseal_store("user-42", material, ttl_seconds=900)
679
+ # Query or write while the grant is live, then remove it when finished.
680
+ client.reseal_store("user-42")
681
+ ```
682
+
683
+ `Client.rotate_store_key("user-42", fresh_material)` replaces the caller-held
684
+ material after the store is unsealed. A sealed data-plane read returns
685
+ `CloudError` with status 423 and a `store_sealed:` detail; applications decide
686
+ when to request an unseal grant.
687
+
659
688
  ## Concurrency & durability
660
689
 
661
690
  - **Single writer, many readers, per path.** One handle holds the path open for *writing* at
@@ -699,7 +728,7 @@ See [`examples/mcp_config.json`](examples/mcp_config.json) for a copy-paste star
699
728
  read-your-writes against an append's returned LSN). On Windows the shared lock degrades to an
700
729
  exclusive hold, so appenders serialize there rather than coexisting. A record is a precomputed
701
730
  vector plus metadata (with an optional caption, e.g. for an image, retained only when the appender
702
- opts into `store_text` -- off by default, so no raw text reaches the WAL); with an embedder
731
+ opts into `store_text`; it is off by default, so no raw text reaches the WAL); with an embedder
703
732
  configured, the appender also ingests full text (chunked by the core, embedded in the binding
704
733
  layer, then logged as a post-embedding record) so text writes are multi-producer too, without a
705
734
  captured base generation. It requires WAL commit mode. Exposed as the native `CoreAppender`, over
@@ -774,4 +803,3 @@ Enterprise licensing and commercial support are available from
774
803
  PRs welcome; see [`CONTRIBUTING.md`](CONTRIBUTING.md). Report security issues **privately**
775
804
  per [`SECURITY.md`](SECURITY.md), not in public issues. Other bugs and requests go to the
776
805
  [issue tracker](https://github.com/Egoist-Machines/LodeDB/issues).
777
-
@@ -0,0 +1,41 @@
1
+ [package]
2
+ name = "lodedb-cloud-core"
3
+ description = "Native transfer core for the LodeDB managed cloud — durable push/pull/sync over the open commit format."
4
+ version.workspace = true
5
+ edition.workspace = true
6
+ rust-version.workspace = true
7
+ license.workspace = true
8
+ repository.workspace = true
9
+ publish = false
10
+
11
+ [dependencies]
12
+ # The engine core. The cloud transfer plane links its `storage` module directly
13
+ # so the managed cloud and the embedded product share ONE commit-format
14
+ # implementation (schema, layout, checksum, sub-manifests incl. `tvmv`). A path
15
+ # dep now that both live in this workspace — engine changes and the transfer
16
+ # plane move together under one suite.
17
+ lodedb-core = { path = "../lodedb-core" }
18
+ # Object-storage backend (S3/GCS/Azure) behind the same ArtifactStore trait. The
19
+ # `aws` feature adds the S3 builder; the crate's conditional-put API (Create /
20
+ # Update{e_tag}) is what makes the root-pointer compare-and-swap strongly
21
+ # consistent on object storage. Its InMemory backend also drives the tests.
22
+ object_store = { version = "0.12.5", features = ["aws"] }
23
+ serde_json = "1.0"
24
+ sha2 = "0.10"
25
+ # Streaming transfer plumbing: `bytes` for the object-store chunk type and
26
+ # `futures` for driving its download streams chunk-by-chunk from the sync
27
+ # ArtifactStore surface. Both are already in the tree via object_store.
28
+ bytes = "1"
29
+ futures = "0.3"
30
+ # The object_store backends are async; the runtime is enabled with net + time so a
31
+ # real S3 client works (not just the in-memory test backend). ObjectArtifactStore
32
+ # keeps the sync ArtifactStore trait by owning a current-thread runtime.
33
+ tokio = { version = "1.47.5", default-features = false, features = ["rt", "net", "time"] }
34
+ # The pointer serialize/validate round-trip reuses lodedb-core's own
35
+ # write/read_commit_manifest (which operate on file paths) via a scratch file, so
36
+ # object-store pointer documents are byte-identical to on-disk ones. Also used by
37
+ # the test fixtures to build committed generations in throwaway directories.
38
+ tempfile = "3"
39
+
40
+ [lints]
41
+ workspace = true
@@ -0,0 +1,127 @@
1
+ //! Storage abstraction for committed, generation-addressed artifacts.
2
+ //!
3
+ //! The substrate is the engine's existing on-disk layout: a committed generation
4
+ //! is a set of immutable, sha256-addressed `g<epoch>.*` artifacts under
5
+ //! `<key>.gen/` pinned by an atomic `<key>.commit.json` root pointer. An
6
+ //! [`ArtifactStore`] exposes exactly the operations that layout needs:
7
+ //!
8
+ //! - **names** are store-relative paths mirroring the on-disk layout, e.g.
9
+ //! `idx.gen/g7.json` or `idx.gen/g7.json.json-delta/delta-00000000.jsd`;
10
+ //! - the **pointer key** is the index key (`idx` -> `idx.commit.json`).
11
+ //!
12
+ //! Implementations treat content artifacts as immutable and content-addressed
13
+ //! (written once, never overwritten), and commit a generation *only* by swapping
14
+ //! the root pointer. [`LocalArtifactStore`](crate::LocalArtifactStore) is the
15
+ //! default; object-storage backends (S3/GCS/Azure) land in later milestones with
16
+ //! the artifact names becoming object keys under a per-tenant prefix and the
17
+ //! pointer swap becoming a conditional write.
18
+
19
+ use crate::error::Result;
20
+ use serde_json::Value;
21
+ use std::io::Read;
22
+
23
+ /// Reads/writes immutable artifacts and swaps a root pointer atomically.
24
+ ///
25
+ /// The interface is deliberately small: streaming artifact I/O plus a pointer
26
+ /// compare-and-swap. That is everything the generation-addressed commit format
27
+ /// needs as a cloud substrate. Streaming is the primitive. Vector bases run
28
+ /// to gigabytes, so a transfer's peak memory must be a fixed buffer, never a
29
+ /// function of artifact size; the buffered methods are conveniences for the
30
+ /// small payloads (pointer documents) built on top.
31
+ pub trait ArtifactStore {
32
+ /// Opens one artifact for streaming reads;
33
+ /// [`ArtifactStoreError::NotFound`] if the name is absent.
34
+ ///
35
+ /// [`ArtifactStoreError::NotFound`]: crate::ArtifactStoreError::NotFound
36
+ fn open_read<'a>(&'a self, name: &str) -> Result<Box<dyn Read + 'a>>;
37
+
38
+ /// Returns one artifact's bytes, fully buffered, for small payloads
39
+ /// only; transfers and verification stream via [`open_read`](Self::open_read).
40
+ fn read_bytes(&self, name: &str) -> Result<Vec<u8>> {
41
+ let mut data = Vec::new();
42
+ self.open_read(name)?.read_to_end(&mut data)?;
43
+ Ok(data)
44
+ }
45
+
46
+ /// Streams one immutable artifact into the store unless it already
47
+ /// exists, hashing incrementally as it copies.
48
+ ///
49
+ /// `sha256` is the expected lowercase-hex digest of the streamed bytes; a
50
+ /// mismatch is an integrity error and nothing is stored, so corruption can
51
+ /// never land, even when the source is another store's live stream. A
52
+ /// name already present with identical bytes is a no-op (idempotent
53
+ /// re-push); present with *different* bytes is a conflict. Artifacts are
54
+ /// immutable and are never overwritten in place. On the already-present
55
+ /// no-op path the incoming stream may be left partially (or wholly)
56
+ /// unread and unvalidated: success then attests that the STORED bytes
57
+ /// match `sha256`, not that the stream did.
58
+ ///
59
+ /// `size_hint` is the expected byte count (0 = unknown), advisory only:
60
+ /// backends use it to pick an upload strategy (the object store's
61
+ /// conditional-claim path has a provider copy-size ceiling), never to
62
+ /// trust the stream's length. Transfers pass the manifest-recorded size.
63
+ fn write_stream_if_absent(
64
+ &self,
65
+ name: &str,
66
+ data: &mut dyn Read,
67
+ sha256: &str,
68
+ size_hint: u64,
69
+ ) -> Result<()>;
70
+
71
+ /// Buffered convenience over
72
+ /// [`write_stream_if_absent`](Self::write_stream_if_absent), for small
73
+ /// payloads a caller already holds in memory.
74
+ fn write_bytes_if_absent(&self, name: &str, data: &[u8], sha256: &str) -> Result<()> {
75
+ let mut cursor = data;
76
+ self.write_stream_if_absent(name, &mut cursor, sha256, data.len() as u64)
77
+ }
78
+
79
+ /// Whether an artifact named `name` is present, without asserting anything
80
+ /// about its content.
81
+ ///
82
+ /// The default opens the artifact and maps
83
+ /// [`NotFound`](crate::ArtifactStoreError::NotFound) to `false`; backends
84
+ /// with a cheap existence primitive (a filesystem stat, an object-store
85
+ /// HEAD) should override it to avoid opening a stream.
86
+ fn contains(&self, name: &str) -> Result<bool> {
87
+ match self.open_read(name) {
88
+ Ok(_) => Ok(true),
89
+ Err(crate::ArtifactStoreError::NotFound(_)) => Ok(false),
90
+ Err(error) => Err(error),
91
+ }
92
+ }
93
+
94
+ /// Returns the committed root-manifest body for `key`, or `None`.
95
+ ///
96
+ /// The body carries the generation number, the base epoch, counts, and the
97
+ /// per-store sub-manifests that name every artifact in the generation. It is
98
+ /// the read side that [`compare_and_swap_pointer`](Self::compare_and_swap_pointer)
99
+ /// and the inventory helpers need to learn the currently-committed generation.
100
+ fn read_pointer(&self, key: &str) -> Result<Option<Value>>;
101
+
102
+ /// Publishes `new_body` as the root pointer iff the currently committed body is
103
+ /// exactly `old_body` (`None` means the pointer must not yet exist); otherwise
104
+ /// [`ArtifactStoreError::PointerConflict`]. This swap is the only commit point:
105
+ /// artifacts are published first, then the pointer flips all-or-nothing.
106
+ ///
107
+ /// The precondition is the full committed body, not just its generation number.
108
+ /// A generation number is not a unique version token (two independent lineages
109
+ /// can share one with different content), so comparing the whole body is what
110
+ /// makes this a sound compare-and-swap rather than an ABA-prone check.
111
+ ///
112
+ /// [`ArtifactStoreError::PointerConflict`]: crate::ArtifactStoreError::PointerConflict
113
+ fn compare_and_swap_pointer(
114
+ &self,
115
+ key: &str,
116
+ old_body: Option<&Value>,
117
+ new_body: &Value,
118
+ ) -> Result<()>;
119
+ }
120
+
121
+ /// The generation number recorded in a committed body, if present.
122
+ ///
123
+ /// Used only to annotate a [`PointerConflict`](crate::ArtifactStoreError::PointerConflict)
124
+ /// for readability; the swap precondition is the full body, not this number.
125
+ pub(crate) fn body_generation(body: &Value) -> Option<u64> {
126
+ body.get("generation").and_then(Value::as_u64)
127
+ }
@@ -0,0 +1,119 @@
1
+ //! Content-addressed blob naming for the managed remote layout.
2
+ //!
3
+ //! A managed remote can store artifacts content-addressed
4
+ //! (`blobs/sha256/aa/<sha256>` under a per-tenant prefix) instead of under
5
+ //! their engine path names. Content addressing is what absorbs the
6
+ //! fork-collision case (two branches committing different artifacts under the
7
+ //! same engine name, e.g. `idx.gen/g7.json`, coexist as two blobs), and the
8
+ //! two-level `aa/` fan-out keeps listings and prefix operations tractable.
9
+ //!
10
+ //! These helpers are pure and tested; they pin the naming contract the
11
+ //! transfer plane builds on. The digest is always the lowercase-hex
12
+ //! SHA-256 of the blob's bytes, the same digest the engine records per
13
+ //! artifact and [`ArtifactStore::write_bytes_if_absent`] verifies.
14
+ //!
15
+ //! [`ArtifactStore::write_bytes_if_absent`]: crate::ArtifactStore::write_bytes_if_absent
16
+
17
+ use crate::error::{ArtifactStoreError, Result};
18
+
19
+ /// The fixed prefix every blob name lives under.
20
+ const BLOB_PREFIX: &str = "blobs/sha256";
21
+
22
+ /// Returns the store-relative blob name for a content digest:
23
+ /// `blobs/sha256/aa/<sha256>`, where `aa` is the digest's first two hex chars.
24
+ ///
25
+ /// Rejects anything that is not exactly 64 lowercase hex characters. Blob
26
+ /// names participate in authorization paths, so a malformed digest must fail
27
+ /// closed rather than mint a name outside the contract.
28
+ pub fn blob_name(sha256: &str) -> Result<String> {
29
+ validate_sha256(sha256)?;
30
+ Ok(format!("{BLOB_PREFIX}/{}/{sha256}", &sha256[..2]))
31
+ }
32
+
33
+ /// Parses a blob name back to its content digest, the exact inverse of
34
+ /// [`blob_name`]. Rejects any name that deviates from the contract (wrong
35
+ /// prefix, fan-out directory disagreeing with the digest, malformed digest).
36
+ pub fn parse_blob_name(name: &str) -> Result<String> {
37
+ let malformed = || {
38
+ ArtifactStoreError::Integrity(format!(
39
+ "blob name {name:?} does not match {BLOB_PREFIX}/aa/<sha256>"
40
+ ))
41
+ };
42
+ let rest = name.strip_prefix(BLOB_PREFIX).ok_or_else(malformed)?;
43
+ let rest = rest.strip_prefix('/').ok_or_else(malformed)?;
44
+ let (fan_out, sha256) = rest.split_once('/').ok_or_else(malformed)?;
45
+ validate_sha256(sha256).map_err(|_| malformed())?;
46
+ if fan_out != &sha256[..2] {
47
+ return Err(malformed());
48
+ }
49
+ Ok(sha256.to_string())
50
+ }
51
+
52
+ /// Requires exactly 64 lowercase hex characters. Also used by the generation
53
+ /// inventory to validate every manifest digest at the trust boundary: digests
54
+ /// become staging file names and object keys, so a non-hex "digest" must fail
55
+ /// closed before it can name a path.
56
+ pub(crate) fn validate_sha256(sha256: &str) -> Result<()> {
57
+ let valid = sha256.len() == 64
58
+ && sha256
59
+ .bytes()
60
+ .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte));
61
+ if valid {
62
+ Ok(())
63
+ } else {
64
+ Err(ArtifactStoreError::Integrity(format!(
65
+ "{sha256:?} is not a lowercase-hex sha256 digest"
66
+ )))
67
+ }
68
+ }
69
+
70
+ #[cfg(test)]
71
+ mod tests {
72
+ use super::{blob_name, parse_blob_name};
73
+ use crate::error::ArtifactStoreError;
74
+
75
+ const SHA: &str = "aa30b1cc05c10ac8a1f4e6d46f7d4b1a9c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f";
76
+
77
+ #[test]
78
+ fn names_a_blob_under_its_two_char_fan_out() {
79
+ assert_eq!(blob_name(SHA).unwrap(), format!("blobs/sha256/aa/{SHA}"));
80
+ }
81
+
82
+ #[test]
83
+ fn parse_inverts_blob_name() {
84
+ assert_eq!(parse_blob_name(&blob_name(SHA).unwrap()).unwrap(), SHA);
85
+ }
86
+
87
+ #[test]
88
+ fn rejects_malformed_digests() {
89
+ for bad in [
90
+ "",
91
+ "abc",
92
+ &SHA[..63], // too short
93
+ &format!("{SHA}0"), // too long
94
+ &SHA.to_uppercase(), // uppercase
95
+ &format!("g{}", &SHA[1..]), // non-hex
96
+ ] {
97
+ assert!(matches!(
98
+ blob_name(bad).unwrap_err(),
99
+ ArtifactStoreError::Integrity(_)
100
+ ));
101
+ }
102
+ }
103
+
104
+ #[test]
105
+ fn rejects_names_off_contract() {
106
+ for bad in [
107
+ format!("blobs/sha256/{SHA}"), // no fan-out level
108
+ format!("blobs/sha256/bb/{SHA}"), // fan-out disagrees
109
+ format!("blobs/md5/aa/{SHA}"), // wrong algorithm segment
110
+ format!("sha256/aa/{SHA}"), // wrong prefix
111
+ format!("blobs/sha256/aa/{}", &SHA[..63]), // malformed digest
112
+ ] {
113
+ assert!(matches!(
114
+ parse_blob_name(&bad).unwrap_err(),
115
+ ArtifactStoreError::Integrity(_)
116
+ ));
117
+ }
118
+ }
119
+ }