tensorfs 0.3.78__tar.gz → 0.3.79__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 (181) hide show
  1. {tensorfs-0.3.78 → tensorfs-0.3.79}/Cargo.lock +2 -2
  2. {tensorfs-0.3.78 → tensorfs-0.3.79}/Cargo.toml +1 -1
  3. {tensorfs-0.3.78 → tensorfs-0.3.79}/PKG-INFO +1 -1
  4. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/ingest.rs +1 -0
  5. tensorfs-0.3.79/crates/tensorfs-core/src/catalog/lock.rs +206 -0
  6. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/catalog.rs +28 -1
  7. tensorfs-0.3.78/crates/tensorfs-core/src/repo_cache/identity.rs → tensorfs-0.3.79/crates/tensorfs-core/src/filesystem_identity.rs +30 -24
  8. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/fill.rs +3 -3
  9. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/convert.rs +10 -12
  10. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/fingerprint.rs +22 -3
  11. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/h3_lora.rs +12 -5
  12. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/preflight.rs +0 -3
  13. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/source.rs +19 -0
  14. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/lib.rs +1 -0
  15. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/meta.rs +67 -14
  16. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/repo_cache.rs +5 -6
  17. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +4 -1
  18. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/tests.rs +31 -20
  19. tensorfs-0.3.79/crates/tensorfs-core/tests/catalog_owner_probe.c +29 -0
  20. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/h3_lora.rs +153 -7
  21. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/source_prefixes.rs +3 -1
  22. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/store_boundaries.rs +42 -16
  23. {tensorfs-0.3.78 → tensorfs-0.3.79}/pyproject.toml +1 -1
  24. tensorfs-0.3.78/crates/tensorfs-core/src/catalog/lock.rs +0 -121
  25. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/Cargo.toml +0 -0
  26. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/build.rs +0 -0
  27. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  28. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  29. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  30. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  31. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/asset_update.rs +0 -0
  32. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/b64.rs +0 -0
  33. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  34. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  35. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/ensure.rs +0 -0
  36. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  37. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
  38. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  39. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
  40. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  41. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  42. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/cache_roots.rs +0 -0
  43. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/canon.rs +0 -0
  44. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/capability.rs +0 -0
  45. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/cbor.rs +0 -0
  46. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  47. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
  48. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/corpus.rs +0 -0
  49. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  50. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/companion_tests.rs +0 -0
  51. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  52. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  53. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/group_commit_tests.rs +0 -0
  54. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/history_cost_tests.rs +0 -0
  55. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/part_rows_tests.rs +0 -0
  56. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  57. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
  58. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/retention.rs +0 -0
  59. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived/roots.rs +0 -0
  60. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/derived.rs +0 -0
  61. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/descriptors.rs +0 -0
  62. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/disk.rs +0 -0
  63. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/dtype.rs +0 -0
  64. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/durability.rs +0 -0
  65. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ensure/admission.rs +0 -0
  66. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ensure/flight.rs +0 -0
  67. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ensure.rs +0 -0
  68. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/err.rs +0 -0
  69. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/fetch.rs +0 -0
  70. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/fit.rs +0 -0
  71. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/gc.rs +0 -0
  72. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/header.rs +0 -0
  73. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/home.rs +0 -0
  74. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ids.rs +0 -0
  75. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  76. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/custody.rs +0 -0
  77. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  78. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  79. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  80. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/narrow.rs +0 -0
  81. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/routes/anima.routes.json +0 -0
  82. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/routes/anima.spec.json +0 -0
  83. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/routes/sdxl.routes.json +0 -0
  84. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/routes/sdxl.spec.json +0 -0
  85. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/routes.rs +0 -0
  86. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  87. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
  88. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/jcs.rs +0 -0
  89. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/limits.rs +0 -0
  90. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/machine.rs +0 -0
  91. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/manifest.rs +0 -0
  92. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/project.rs +0 -0
  93. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/providers.rs +0 -0
  94. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/read.rs +0 -0
  95. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/receipt.rs +0 -0
  96. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/reclaim.rs +0 -0
  97. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/registry.rs +0 -0
  98. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/relation.rs +0 -0
  99. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/repository.rs +0 -0
  100. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/sha256.rs +0 -0
  101. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/source_artifact.rs +0 -0
  102. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/spec.rs +0 -0
  103. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/staging.rs +0 -0
  104. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/stats.rs +0 -0
  105. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/storage.rs +0 -0
  106. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/store.rs +0 -0
  107. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/api.rs +0 -0
  108. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/http.rs +0 -0
  109. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/hub.rs +0 -0
  110. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  111. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/memory.rs +0 -0
  112. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  113. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/pace.rs +0 -0
  114. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  115. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
  116. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/pull.rs +0 -0
  117. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/push.rs +0 -0
  118. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
  119. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/transport/speculate.rs +0 -0
  120. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/unsynced.rs +0 -0
  121. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/src/vectors.rs +0 -0
  122. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/additive_fields.rs +0 -0
  123. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/anima_hf_profile.rs +0 -0
  124. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/as_is.rs +0 -0
  125. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
  126. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/civitai_anima.rs +0 -0
  127. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/civitai_sdxl_profile.rs +0 -0
  128. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/ensure.rs +0 -0
  129. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fd_budget.rs +0 -0
  130. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fd_headroom.rs +0 -0
  131. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  132. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fit.rs +0 -0
  133. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  134. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  135. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  136. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  137. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  138. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  139. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  140. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
  141. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  142. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/home.rs +0 -0
  143. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/hub_tls.rs +0 -0
  144. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  145. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  146. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/liveness.rs +0 -0
  147. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/qwen_image21_profile.rs +0 -0
  148. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/read_plan.rs +0 -0
  149. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  150. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  151. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  152. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  153. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  154. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/routed_single_file.rs +0 -0
  155. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  156. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  157. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  158. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/source_download.rs +0 -0
  159. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  160. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
  161. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/staging.rs +0 -0
  162. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  163. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/transfer_memory.rs +0 -0
  164. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/trust_index.rs +0 -0
  165. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/version.rs +0 -0
  166. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-core/tests/write_through.rs +0 -0
  167. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-py/Cargo.toml +0 -0
  168. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-py/src/docs.rs +0 -0
  169. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-py/src/ensure.rs +0 -0
  170. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-py/src/errors.rs +0 -0
  171. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-py/src/ingest.rs +0 -0
  172. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-py/src/lib.rs +0 -0
  173. {tensorfs-0.3.78 → tensorfs-0.3.79}/crates/tensorfs-py/src/store.rs +0 -0
  174. {tensorfs-0.3.78 → tensorfs-0.3.79}/python/tensorfs/__init__.py +0 -0
  175. {tensorfs-0.3.78 → tensorfs-0.3.79}/python/tensorfs/_derived_channel.py +0 -0
  176. {tensorfs-0.3.78 → tensorfs-0.3.79}/python/tensorfs/_ext.pyi +0 -0
  177. {tensorfs-0.3.78 → tensorfs-0.3.79}/python/tensorfs/_source_channel.py +0 -0
  178. {tensorfs-0.3.78 → tensorfs-0.3.79}/python/tensorfs/_tensors.py +0 -0
  179. {tensorfs-0.3.78 → tensorfs-0.3.79}/python/tensorfs/derived.py +0 -0
  180. {tensorfs-0.3.78 → tensorfs-0.3.79}/python/tensorfs/errors.py +0 -0
  181. {tensorfs-0.3.78 → tensorfs-0.3.79}/python/tensorfs/py.typed +0 -0
@@ -439,7 +439,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
439
439
 
440
440
  [[package]]
441
441
  name = "tensorfs-core"
442
- version = "0.3.78"
442
+ version = "0.3.79"
443
443
  dependencies = [
444
444
  "fs2",
445
445
  "libc",
@@ -454,7 +454,7 @@ dependencies = [
454
454
 
455
455
  [[package]]
456
456
  name = "tensorfs-py"
457
- version = "0.3.78"
457
+ version = "0.3.79"
458
458
  dependencies = [
459
459
  "pyo3",
460
460
  "tensorfs-core",
@@ -3,5 +3,5 @@ resolver = "2"
3
3
  members = ["crates/tensorfs-core", "crates/tensorfs-py"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.3.78"
6
+ version = "0.3.79"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.78
3
+ Version: 0.3.79
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
@@ -574,6 +574,7 @@ pub fn cmd_bank(dialect: &str, converter: &str, f: &Flags) -> ExitCode {
574
574
  return ExitCode::FAILURE;
575
575
  }
576
576
  let profile = SourceProfile {
577
+ auto_select: true,
577
578
  name: name.to_string(),
578
579
  components,
579
580
  };
@@ -0,0 +1,206 @@
1
+ //! Waiting for another process's catalog lock by whether its holder can still release it.
2
+ //!
3
+ //! SQLite's locks are POSIX locks: they die with the process that holds them. A holder
4
+ //! that is alive is committing or about to, so a writer waits for it for as long as it
5
+ //! takes; a flat deadline refused a busy, progressing writer as if it were dead. The only
6
+ //! holder that cannot release is one that is stopped (a debugger or SIGSTOP), and only
7
+ //! then does a statement give up. Dead processes have already released their locks.
8
+ //!
9
+ //! Holders come from `/proc/locks`, matched by the catalog's inode (and its WAL `-shm`).
10
+ //! The catalog file itself is never opened here: closing a second descriptor on it would
11
+ //! drop every POSIX lock this process holds on it.
12
+
13
+ use std::cell::RefCell;
14
+ use std::path::{Path, PathBuf};
15
+ use std::time::Duration;
16
+
17
+ use rusqlite::Connection;
18
+
19
+ use crate::err::Result;
20
+
21
+ thread_local! {
22
+ /// The catalog this thread's statements wait on. A connection is opened for each
23
+ /// operation just before it is used, on the thread that uses it, so the one opened last
24
+ /// here is the one a busy statement belongs to.
25
+ static WAITING_ON: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
26
+ }
27
+
28
+ /// Install the waiting handler on one connection.
29
+ pub(crate) fn wait_for_live_holders(connection: &Connection, path: &Path) -> Result<()> {
30
+ WAITING_ON.with(|waiting| *waiting.borrow_mut() = Some(path.to_path_buf()));
31
+ connection
32
+ .busy_handler(Some(busy))
33
+ .map_err(|error| super::sql("install catalog lock wait", error))
34
+ }
35
+
36
+ fn busy(attempt: i32) -> bool {
37
+ let path = WAITING_ON.with(|waiting| waiting.borrow().clone());
38
+ if path.is_some_and(|path| holders_cannot_release(&path)) {
39
+ return false;
40
+ }
41
+ std::thread::sleep(Duration::from_millis(attempt.clamp(1, 64) as u64));
42
+ true
43
+ }
44
+
45
+ /// Every currently held write lock belongs to a stopped process. Without a current
46
+ /// holder to name (the lock just went, or no `/proc`), keep asking.
47
+ fn holders_cannot_release(path: &Path) -> bool {
48
+ let files: Vec<(u64, u64)> = [path.to_path_buf(), shm(path)]
49
+ .iter()
50
+ .filter_map(|file| std::fs::metadata(file).ok())
51
+ .map(|metadata| {
52
+ use std::os::unix::fs::MetadataExt;
53
+ (metadata.dev(), metadata.ino())
54
+ })
55
+ .collect();
56
+ let Ok(locks) = std::fs::read_to_string("/proc/locks") else {
57
+ return false;
58
+ };
59
+ snapshots_cannot_release(&locks, &files, std::process::id(), stopped, || {
60
+ std::fs::read_to_string("/proc/locks").ok()
61
+ })
62
+ }
63
+
64
+ fn snapshots_cannot_release(
65
+ observed: &str,
66
+ files: &[(u64, u64)],
67
+ own: u32,
68
+ is_stopped: impl Fn(u32) -> bool,
69
+ current: impl FnOnce() -> Option<String>,
70
+ ) -> bool {
71
+ let holders = write_holders(observed, files, own);
72
+ if holders.is_empty() || !holders.iter().all(|pid| is_stopped(*pid)) {
73
+ return false;
74
+ }
75
+ // A holder may unlock or exit between the lock snapshot and its process-state
76
+ // read. Refuse only if those stopped processes still hold the actual locks.
77
+ let Some(current) = current() else {
78
+ return false;
79
+ };
80
+ let current = write_holders(&current, files, own);
81
+ !current.is_empty()
82
+ && current
83
+ .iter()
84
+ .all(|pid| holders.contains(pid) && is_stopped(*pid))
85
+ }
86
+
87
+ fn shm(path: &Path) -> PathBuf {
88
+ let mut name = path.as_os_str().to_owned();
89
+ name.push("-shm");
90
+ PathBuf::from(name)
91
+ }
92
+
93
+ /// Other processes' POSIX write locks on these inodes, from `/proc/locks` lines such as
94
+ /// `3: POSIX ADVISORY WRITE 167671 103:02:204736108 1073741824 1073742335`.
95
+ fn write_holders(locks: &str, files: &[(u64, u64)], own: u32) -> Vec<u32> {
96
+ locks
97
+ .lines()
98
+ .filter_map(|line| {
99
+ let fields: Vec<&str> = line.split_whitespace().collect();
100
+ let at = fields.iter().position(|field| *field == "POSIX")?;
101
+ if fields[..at].contains(&"->") || fields.get(at + 2) != Some(&"WRITE") {
102
+ return None;
103
+ }
104
+ let pid: u32 = fields.get(at + 3)?.parse().ok()?;
105
+ let mut file = fields.get(at + 4)?.split(':');
106
+ let major = u32::from_str_radix(file.next()?, 16).ok()?;
107
+ let minor = u32::from_str_radix(file.next()?, 16).ok()?;
108
+ let inode: u64 = file.next()?.parse().ok()?;
109
+ let device = rustix::fs::makedev(major, minor) as u64;
110
+ (file.next().is_none() && pid != own && files.contains(&(device, inode))).then_some(pid)
111
+ })
112
+ .collect()
113
+ }
114
+
115
+ /// Only stopped and traced-stopped processes keep locks they cannot release.
116
+ fn stopped(pid: u32) -> bool {
117
+ std::fs::read_to_string(format!("/proc/{pid}/stat"))
118
+ .ok()
119
+ .is_some_and(|stat| process_is_stopped(&stat))
120
+ }
121
+
122
+ fn process_is_stopped(stat: &str) -> bool {
123
+ stat.rsplit_once(')')
124
+ .and_then(|(_, fields)| fields.split_whitespace().next())
125
+ .is_some_and(|state| matches!(state, "T" | "t"))
126
+ }
127
+
128
+ #[cfg(test)]
129
+ mod tests {
130
+ use super::*;
131
+
132
+ #[test]
133
+ fn only_other_processes_write_locks_on_the_catalog_are_holders() {
134
+ let locks = "\
135
+ 1: POSIX ADVISORY WRITE 4242 103:02:77 1073741824 1073742335
136
+ 2: POSIX ADVISORY READ 5151 103:02:77 1073741826 1073742335
137
+ 3: POSIX ADVISORY WRITE 6161 103:02:99 120 120
138
+ 4: FLOCK ADVISORY WRITE 7171 103:02:77 0 EOF
139
+ 5: POSIX ADVISORY WRITE 1000 103:02:77 0 EOF
140
+ 6: -> POSIX ADVISORY WRITE 8181 103:02:77 0 EOF
141
+ 7: POSIX ADVISORY WRITE 9191 104:02:77 0 EOF
142
+ ";
143
+ let device = rustix::fs::makedev(0x103, 2) as u64;
144
+ assert_eq!(write_holders(locks, &[(device, 77)], 1000), vec![4242]);
145
+ assert_eq!(
146
+ write_holders(locks, &[(device, 77), (device, 99)], 1000),
147
+ vec![4242, 6161]
148
+ );
149
+ assert!(write_holders(locks, &[(device, 5)], 1000).is_empty());
150
+ }
151
+
152
+ #[test]
153
+ fn exited_holder_is_progress_and_never_a_stopped_writer() {
154
+ for state in ["Z", "X", "R", "S", "D"] {
155
+ assert!(!process_is_stopped(&format!("4242 (writer) {state} 1 2 3")));
156
+ }
157
+ for state in ["T", "t"] {
158
+ assert!(process_is_stopped(&format!("4242 (writer) {state} 1 2 3")));
159
+ }
160
+ assert!(!process_is_stopped("unavailable"));
161
+ }
162
+
163
+ #[test]
164
+ fn stopped_holder_must_still_own_the_lock_before_refusal() {
165
+ let held = "1: POSIX ADVISORY WRITE 4242 103:02:77 0 EOF\n";
166
+ let files = [(rustix::fs::makedev(0x103, 2) as u64, 77)];
167
+ // Stable SIGSTOP remains a typed refusal, while snapshot -> unlock ->
168
+ // SIGSTOP must let SQLite retry rather than refuse the old snapshot.
169
+ assert!(snapshots_cannot_release(
170
+ held,
171
+ &files,
172
+ 1000,
173
+ |_| true,
174
+ || Some(held.into())
175
+ ));
176
+ assert!(!snapshots_cannot_release(
177
+ held,
178
+ &files,
179
+ 1000,
180
+ |_| true,
181
+ || Some(String::new())
182
+ ));
183
+ assert!(!snapshots_cannot_release(
184
+ held,
185
+ &files,
186
+ 1000,
187
+ |_| true,
188
+ || None
189
+ ));
190
+ let replacement = held.replace("4242", "6161");
191
+ assert!(!snapshots_cannot_release(
192
+ held,
193
+ &files,
194
+ 1000,
195
+ |_| true,
196
+ || Some(replacement)
197
+ ));
198
+ assert!(!snapshots_cannot_release(
199
+ held,
200
+ &files,
201
+ 1000,
202
+ |_| false,
203
+ || panic!("a live holder must retry")
204
+ ));
205
+ }
206
+ }
@@ -104,7 +104,14 @@ pub fn connections_opened(root: &Path) -> u64 {
104
104
  .unwrap_or(0)
105
105
  }
106
106
 
107
- fn connect(path: &Path) -> Result<Connection> {
107
+ pub(crate) fn connect(path: &Path) -> Result<Connection> {
108
+ // SQLite creates WAL/SHM files before assigning their database owner. Opening
109
+ // them under root exposes a transient root-owned inode to concurrent owner
110
+ // connections, which can permanently fall back to read-only WAL access.
111
+ let _identity = crate::filesystem_identity::FilesystemIdentity::for_store_root(
112
+ path.parent().unwrap_or(path),
113
+ )
114
+ .map_err(|error| io("enter catalog filesystem identity", error))?;
108
115
  *opened()
109
116
  .lock()
110
117
  .unwrap_or_else(|error| error.into_inner())
@@ -253,6 +260,26 @@ impl Trust {
253
260
  self.load(&mut kept).map(|_| ())
254
261
  }
255
262
 
263
+ /// Finish setup on the already-validated reader connection. The callback is
264
+ /// synchronous under its mutex; no second setup connection or credential
265
+ /// lifetime escapes into the returned value.
266
+ pub(crate) fn with_connection<T>(
267
+ &self,
268
+ use_connection: impl FnOnce(&mut Connection) -> Result<T>,
269
+ ) -> Result<T> {
270
+ let mut kept = self.kept.lock().unwrap_or_else(|error| error.into_inner());
271
+ self.load(&mut kept)?;
272
+ let connection = &mut kept.as_mut().expect("validated connection").connection;
273
+ // A kept connection can be used by another Runtime thread later. Install
274
+ // the lock context on the thread actually executing this operation.
275
+ lock::wait_for_live_holders(connection, &self.path)?;
276
+ let _identity = crate::filesystem_identity::FilesystemIdentity::for_store_root(
277
+ self.path.parent().unwrap_or(&self.path),
278
+ )
279
+ .map_err(|error| io("enter catalog filesystem identity", error))?;
280
+ use_connection(connection)
281
+ }
282
+
256
283
  fn miss(&self, sha256: &str) -> Option<crate::store::VRecord> {
257
284
  let mut kept = self.kept.lock().unwrap_or_else(|error| error.into_inner());
258
285
  if self.load(&mut kept).ok()? {
@@ -1,11 +1,10 @@
1
- //! Scope optional cache writes to the local Store owner on root-squashed NFS.
1
+ //! Scope synchronous filesystem I/O to the trusted local Store owner.
2
2
  //!
3
3
  //! Only Linux filesystem IDs change, only on the current native thread, and only
4
- //! while synchronous cache I/O runs. Real/effective IDs, other Runtime threads,
5
- //! existing cache permissions, and the configured cache path are unchanged.
4
+ //! while Store I/O runs. Real/effective IDs, other Runtime threads, existing
5
+ //! permissions, and configured paths are unchanged.
6
6
 
7
- use super::SoftFailure;
8
- use crate::store::Store;
7
+ use std::{io, path::Path};
9
8
 
10
9
  #[cfg(target_os = "linux")]
11
10
  mod linux {
@@ -16,7 +15,7 @@ mod linux {
16
15
 
17
16
  use nix::unistd::{geteuid, setfsgid, setfsuid, Gid, Uid};
18
17
 
19
- use super::{SoftFailure, Store};
18
+ use super::{io, Path};
20
19
 
21
20
  // Linux has no getfsuid/getfsgid. An invalid ID cannot be installed; each
22
21
  // setter returns the previous/current ID even when the attempted set fails.
@@ -27,14 +26,14 @@ mod linux {
27
26
  )
28
27
  }
29
28
 
30
- pub(in crate::repo_cache) struct CacheIdentity {
29
+ pub(crate) struct FilesystemIdentity {
31
30
  previous: Option<(Uid, Gid)>,
32
31
  // A credential guard must be destroyed by the exact thread that made it.
33
32
  _thread: PhantomData<Rc<()>>,
34
33
  }
35
34
 
36
- impl CacheIdentity {
37
- pub(in crate::repo_cache) fn for_store(store: &Store) -> Result<Self, SoftFailure> {
35
+ impl FilesystemIdentity {
36
+ pub(crate) fn for_store_root(root: &Path) -> io::Result<Self> {
38
37
  let mut guard = Self {
39
38
  previous: None,
40
39
  _thread: PhantomData,
@@ -44,9 +43,12 @@ mod linux {
44
43
  }
45
44
  // Authority is the already-open LOCAL Store's owner, never a UID
46
45
  // supplied by an artifact, request, or remote cache directory.
47
- let owner = fs::symlink_metadata(store.root()).map_err(|_| SoftFailure::Unavailable)?;
46
+ let owner = fs::symlink_metadata(root)?;
48
47
  if !owner.is_dir() || owner.uid() == u32::MAX || owner.gid() == u32::MAX {
49
- return Err(SoftFailure::Unavailable);
48
+ return Err(io::Error::new(
49
+ io::ErrorKind::PermissionDenied,
50
+ "cannot enter Store filesystem identity",
51
+ ));
50
52
  }
51
53
  let desired = (Uid::from_raw(owner.uid()), Gid::from_raw(owner.gid()));
52
54
  let previous = filesystem_ids();
@@ -58,14 +60,17 @@ mod linux {
58
60
  setfsuid(desired.0);
59
61
  if filesystem_ids() != desired {
60
62
  // Drop restores either field that changed before refusing the
61
- // optional cache write. The origin/local operation remains valid.
62
- return Err(SoftFailure::Unavailable);
63
+ // synchronous I/O. The caller receives the identity refusal.
64
+ return Err(io::Error::new(
65
+ io::ErrorKind::PermissionDenied,
66
+ "cannot enter Store filesystem identity",
67
+ ));
63
68
  }
64
69
  Ok(guard)
65
70
  }
66
71
  }
67
72
 
68
- impl Drop for CacheIdentity {
73
+ impl Drop for FilesystemIdentity {
69
74
  fn drop(&mut self) {
70
75
  let Some((uid, gid)) = self.previous else {
71
76
  return;
@@ -74,7 +79,7 @@ mod linux {
74
79
  setfsgid(gid);
75
80
  if filesystem_ids() != (uid, gid) {
76
81
  // Continuing a reused Runtime thread under the wrong identity is
77
- // unsafe. No cache error can reach here: this is a failed kernel
82
+ // unsafe. No I/O error can reach here: this is a failed kernel
78
83
  // restoration of credentials that this thread previously held.
79
84
  std::process::abort();
80
85
  }
@@ -84,6 +89,7 @@ mod linux {
84
89
  #[cfg(test)]
85
90
  mod tests {
86
91
  use super::*;
92
+ use crate::store::Store;
87
93
  use std::panic::{catch_unwind, AssertUnwindSafe};
88
94
  use std::sync::{Arc, Barrier};
89
95
  use std::time::{SystemTime, UNIX_EPOCH};
@@ -117,7 +123,7 @@ mod linux {
117
123
  other_observed.wait();
118
124
  });
119
125
  {
120
- let _guard = CacheIdentity::for_store(&store).unwrap();
126
+ let _guard = FilesystemIdentity::for_store_root(store.root()).unwrap();
121
127
  assert_eq!(filesystem_ids(), desired);
122
128
  assert!(geteuid().is_root());
123
129
  entered.wait();
@@ -129,14 +135,14 @@ mod linux {
129
135
  }
130
136
  assert_eq!(filesystem_ids(), original);
131
137
  });
132
- let failure: Result<(), SoftFailure> = (|| {
133
- let _guard = CacheIdentity::for_store(&store)?;
134
- Err(SoftFailure::Unavailable)
138
+ let failure: io::Result<()> = (|| {
139
+ let _guard = FilesystemIdentity::for_store_root(store.root())?;
140
+ Err(io::Error::other("owned fixture failure"))
135
141
  })();
136
142
  assert!(failure.is_err());
137
143
  assert_eq!(filesystem_ids(), original);
138
144
  let panic = catch_unwind(AssertUnwindSafe(|| {
139
- let _guard = CacheIdentity::for_store(&store).unwrap();
145
+ let _guard = FilesystemIdentity::for_store_root(store.root()).unwrap();
140
146
  panic!("cache operation unwind");
141
147
  }));
142
148
  assert!(panic.is_err());
@@ -148,14 +154,14 @@ mod linux {
148
154
  }
149
155
 
150
156
  #[cfg(target_os = "linux")]
151
- pub(super) use linux::CacheIdentity;
157
+ pub(crate) use linux::FilesystemIdentity;
152
158
 
153
159
  #[cfg(not(target_os = "linux"))]
154
- pub(super) struct CacheIdentity;
160
+ pub(crate) struct FilesystemIdentity;
155
161
 
156
162
  #[cfg(not(target_os = "linux"))]
157
- impl CacheIdentity {
158
- pub(super) fn for_store(_: &Store) -> Result<Self, SoftFailure> {
163
+ impl FilesystemIdentity {
164
+ pub(crate) fn for_store_root(_: &Path) -> io::Result<Self> {
159
165
  Ok(Self)
160
166
  }
161
167
  }
@@ -299,9 +299,9 @@ impl Xform {
299
299
  Ok(())
300
300
  }
301
301
 
302
- /// The inverse read-back, which is what makes the wrong split structurally
303
- /// unproducible rather than merely documented: slice the source on the transform's own
304
- /// indexing and the three members must reassemble the source bit-for-bit.
302
+ /// The inverse read-back proves that the transform loses or duplicates no bytes.
303
+ /// It uses the transform's own indexing, so it cannot establish the producer's
304
+ /// QKV layout: that needs an independent projection-output regression.
305
305
  pub fn reassembles(&self, src: &[u8]) -> Result<bool> {
306
306
  match self {
307
307
  Xform::Transpose {
@@ -105,10 +105,10 @@ pub const CONVERTERS: [Converter; 8] = [
105
105
  reference: None,
106
106
  },
107
107
  Converter {
108
- name: "h3.lora/1",
108
+ name: "h3.lora/2",
109
109
  dialect: "safetensors.h3_lora",
110
110
  class: Class::Permutation,
111
- doc: "Native H3 LoRA A/B and Kohya down/up factors: split head-interleaved QKV B rows, swap SwiGLU B halves, preserve A and alpha without arithmetic.",
111
+ doc: "Trainer H3 LoRA A/B and Kohya down/up factors: split contiguous Q/K/V B thirds, swap SwiGLU B halves, preserve A and alpha without arithmetic. Original base checkpoint head interleaving belongs to h3.native/2, not this adapter state-dict dialect.",
112
112
  validated_on_real_bytes: false,
113
113
  normalization: None,
114
114
  lane: None,
@@ -1057,7 +1057,7 @@ fn plan_carrier(
1057
1057
  cands: &Candidates<'_>,
1058
1058
  p: &mut Plan,
1059
1059
  ) -> Result<()> {
1060
- if conv.name == "h3.lora/1" {
1060
+ if conv.name == "h3.lora/2" {
1061
1061
  if allowed != ["plain/1"] {
1062
1062
  return refuse(
1063
1063
  Code::UNKNOWN_ENCODING,
@@ -1913,14 +1913,12 @@ pub fn h3_geometry(
1913
1913
  /// The frozen golden cases for one converter: geometries whose reassembly is checked live,
1914
1914
  /// on real byte patterns, before the converter is allowed to touch an artifact.
1915
1915
  ///
1916
- /// The RED companion is structural rather than a separate fixture: `Xform::reassembles`
1917
- /// slices the source on the transform's own indexing and demands the three members put the
1918
- /// source back bit-for-bit. A flat three-way split of a 56-group source passes every
1919
- /// name/dtype/shape check and FAILS this, which is the whole point — the quarry pinned that
1920
- /// error at ~90% numerically while nothing raised.
1916
+ /// `Xform::reassembles` checks that each declared transform preserves the source bytes.
1917
+ /// Both a contiguous split and a head-interleaved split can round-trip their own indexing;
1918
+ /// the producer's layout needs an independent projected-output test (see `h3_lora`).
1921
1919
  pub fn golden_cases(c: &Converter) -> Vec<(&'static str, Xform)> {
1922
1920
  match c.name {
1923
- "h3.lora/1" => vec![
1921
+ "h3.lora/2" => vec![
1924
1922
  (
1925
1923
  "H3 LoRA SwiGLU B rows",
1926
1924
  Xform::SwapHalves {
@@ -1928,12 +1926,12 @@ pub fn golden_cases(c: &Converter) -> Vec<(&'static str, Xform)> {
1928
1926
  },
1929
1927
  ),
1930
1928
  (
1931
- "H3 LoRA QKV B rows",
1929
+ "H3 trainer LoRA contiguous QKV B thirds",
1932
1930
  Xform::QkvSplit {
1933
- groups: H3_HEADS,
1931
+ groups: 1,
1934
1932
  shares: [1, 1, 1],
1935
1933
  take: 0,
1936
- unit_bytes: H3_HEAD_DIM * 16 * 2,
1934
+ unit_bytes: H3_HEADS * H3_HEAD_DIM * 16 * 2,
1937
1935
  },
1938
1936
  ),
1939
1937
  ],
@@ -201,6 +201,9 @@ pub struct FingerprintRegistry {
201
201
  #[derive(Debug, Clone, PartialEq, Eq)]
202
202
  pub struct SourceProfile {
203
203
  pub name: String,
204
+ /// Whether headers alone may choose this format. False requires the caller
205
+ /// to declare the reviewed format; shapes cannot prove semantic row layout.
206
+ pub auto_select: bool,
204
207
  pub components: Vec<SourceProfileComponent>,
205
208
  }
206
209
 
@@ -612,7 +615,7 @@ impl FingerprintRegistry {
612
615
  self.source_profiles
613
616
  .iter()
614
617
  .map(|profile| {
615
- Value::obj(vec![
618
+ let mut fields = vec![
616
619
  (
617
620
  "components",
618
621
  Value::arr(
@@ -677,7 +680,11 @@ impl FingerprintRegistry {
677
680
  ),
678
681
  ),
679
682
  ("name", Value::str(profile.name.clone())),
680
- ])
683
+ ];
684
+ if !profile.auto_select {
685
+ fields.push(("auto_select", Value::Bool(false)));
686
+ }
687
+ Value::obj(fields)
681
688
  })
682
689
  .collect(),
683
690
  ),
@@ -692,6 +699,11 @@ fn parse_source_profiles(value: &Value) -> Result<Vec<SourceProfile>> {
692
699
  for value in as_arr("FingerprintRegistry", "source_profiles", value)? {
693
700
  let mut fields = Fields::new("SourceProfile", value)?;
694
701
  let name = fields.req_str("name")?.to_string();
702
+ let auto_select = match fields.opt("auto_select") {
703
+ None => true,
704
+ Some(Value::Bool(value)) => *value,
705
+ Some(_) => return refuse(Code::WRONG_TYPE, "SourceProfile.auto_select must be bool"),
706
+ };
695
707
  ascii_name("source profile", &name, limits::MAX_NAME_BYTES)?;
696
708
  let mut components = Vec::new();
697
709
  for value in as_arr("SourceProfile", "components", fields.req("components")?)? {
@@ -838,7 +850,11 @@ fn parse_source_profiles(value: &Value) -> Result<Vec<SourceProfile>> {
838
850
  format!("source profile {name:?} repeats a component"),
839
851
  );
840
852
  }
841
- profiles.push(SourceProfile { name, components });
853
+ profiles.push(SourceProfile {
854
+ name,
855
+ auto_select,
856
+ components,
857
+ });
842
858
  }
843
859
  let mut names: Vec<&str> = profiles
844
860
  .iter()
@@ -1044,10 +1060,12 @@ mod tests {
1044
1060
  let mut registry = registry();
1045
1061
  registry.source_profiles = vec![
1046
1062
  SourceProfile {
1063
+ auto_select: true,
1047
1064
  name: "profile/a".into(),
1048
1065
  components: vec![source_component(Some("a/model.index.json"), None)],
1049
1066
  },
1050
1067
  SourceProfile {
1068
+ auto_select: true,
1051
1069
  name: "profile/b".into(),
1052
1070
  components: vec![
1053
1071
  source_component(Some("b/model.safetensors"), None),
@@ -1070,6 +1088,7 @@ mod tests {
1070
1088
  fn exact_source_members_refuses_missing_or_non_exact_profiles() {
1071
1089
  let mut registry = registry();
1072
1090
  registry.source_profiles = vec![SourceProfile {
1091
+ auto_select: true,
1073
1092
  name: "profile/prefix".into(),
1074
1093
  components: vec![source_component(None, Some("weights/"))],
1075
1094
  }];
@@ -1,9 +1,12 @@
1
- //! Reviewed native H3 LoRA dialects -> canonical projection factors.
1
+ //! Reviewed trainer H3 LoRA dialects -> canonical projection factors.
2
2
  //!
3
3
  //! These adapters omit base Q/K norm tensors, so this dialect declares the exact
4
4
  //! H3 geometry (56 heads of 128). Every target shape must agree before a plan is
5
- //! emitted. This is structural conversion, not permission to apply a FL2VA-trained
6
- //! adapter to REF2VA. Runtime owns that binding and never parses foreign keys.
5
+ //! emitted. Trainer QKV factors use contiguous Q/K/V thirds: the original base
6
+ //! checkpoint's head interleave is reordered before training, not exported again
7
+ //! in adapter state dictionaries. This is structural conversion, not permission
8
+ //! to apply a FL2VA-trained adapter to REF2VA. Runtime owns that binding and never
9
+ //! parses foreign keys.
7
10
 
8
11
  use std::collections::BTreeMap;
9
12
 
@@ -229,10 +232,14 @@ pub(super) fn plan(
229
232
  let transform = if leaf == "attn.qkv_proj" {
230
233
  shape[0] /= 3;
231
234
  Some(Xform::QkvSplit {
232
- groups: H3_HEADS,
235
+ groups: 1,
233
236
  shares: [1, 1, 1],
234
237
  take: take as u8,
235
- unit_bytes: checked_bytes(&b.key, &[H3_HEAD_DIM, a.shape[0]], b.dtype)?,
238
+ unit_bytes: checked_bytes(
239
+ &b.key,
240
+ &[H3_HEADS * H3_HEAD_DIM, a.shape[0]],
241
+ b.dtype,
242
+ )?,
236
243
  })
237
244
  } else if leaf == "mlp.fc1" {
238
245
  Some(Xform::SwapHalves {
@@ -340,9 +340,6 @@ pub fn select_members(
340
340
  .collect()
341
341
  };
342
342
  let (own, all) = (carriers(false), carriers(true));
343
- if profiles.is_empty() && all.len() == 1 {
344
- return Ok(all);
345
- }
346
343
  let heads = read_heads(&resolution.select(&all)?)?;
347
344
  let plan_over = |carriers: &[String]| -> Result<(Vec<String>, bool)> {
348
345
  let members = resolution.select(carriers)?.members;