tensorfs 0.3.61__tar.gz → 0.3.63__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 (155) hide show
  1. {tensorfs-0.3.61 → tensorfs-0.3.63}/Cargo.lock +2 -2
  2. {tensorfs-0.3.61 → tensorfs-0.3.63}/Cargo.toml +1 -1
  3. {tensorfs-0.3.61 → tensorfs-0.3.63}/PKG-INFO +1 -1
  4. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/asset_update.rs +3 -8
  5. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/main.rs +8 -6
  6. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/repo.rs +10 -19
  7. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/snap.rs +4 -1
  8. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/canon.rs +25 -0
  9. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/checkpoint.rs +7 -10
  10. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/checkpoint_root.rs +21 -3
  11. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived/companion_tests.rs +1 -1
  12. tensorfs-0.3.63/crates/tensorfs-core/src/derived/part_rows_tests.rs +169 -0
  13. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived/resume_tests.rs +2 -1
  14. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived/retention.rs +18 -1
  15. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived/roots.rs +140 -1
  16. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived.rs +204 -88
  17. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/transaction.rs +27 -5
  18. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/lib.rs +3 -0
  19. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/manifest.rs +50 -13
  20. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/meta.rs +199 -7
  21. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/project.rs +2 -2
  22. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/source_artifact.rs +20 -3
  23. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/storage.rs +3 -3
  24. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/http.rs +26 -19
  25. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +84 -0
  26. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/push.rs +5 -1
  27. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/additive_fields.rs +43 -13
  28. tensorfs-0.3.63/crates/tensorfs-core/tests/anima_hf_profile.rs +166 -0
  29. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/checkpoint_roots.rs +26 -0
  30. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/qwen_image21_profile.rs +5 -4
  31. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-py/src/store.rs +8 -1
  32. {tensorfs-0.3.61 → tensorfs-0.3.63}/pyproject.toml +1 -1
  33. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/Cargo.toml +0 -0
  34. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/build.rs +0 -0
  35. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  36. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  37. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  38. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  39. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/b64.rs +0 -0
  40. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  41. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  42. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  43. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
  44. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  45. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  46. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/cache_roots.rs +0 -0
  47. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/capability.rs +0 -0
  48. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/catalog.rs +0 -0
  49. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/cbor.rs +0 -0
  50. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/corpus.rs +0 -0
  51. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  52. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  53. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  54. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  55. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/dtype.rs +0 -0
  56. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/durability.rs +0 -0
  57. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/err.rs +0 -0
  58. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/fetch.rs +0 -0
  59. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/fill.rs +0 -0
  60. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/fit.rs +0 -0
  61. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/gc.rs +0 -0
  62. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/header.rs +0 -0
  63. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/home.rs +0 -0
  64. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ids.rs +0 -0
  65. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  66. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
  67. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/custody.rs +0 -0
  68. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
  69. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  70. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
  71. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  72. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  73. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
  74. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/source.rs +0 -0
  75. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  76. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/jcs.rs +0 -0
  77. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/limits.rs +0 -0
  78. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/machine.rs +0 -0
  79. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/providers.rs +0 -0
  80. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/read.rs +0 -0
  81. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/receipt.rs +0 -0
  82. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/reclaim.rs +0 -0
  83. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/registry.rs +0 -0
  84. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/relation.rs +0 -0
  85. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/repo_cache/identity.rs +0 -0
  86. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/repo_cache.rs +0 -0
  87. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/repository.rs +0 -0
  88. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/sha256.rs +0 -0
  89. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/spec.rs +0 -0
  90. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/staging.rs +0 -0
  91. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/stats.rs +0 -0
  92. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/store.rs +0 -0
  93. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/api.rs +0 -0
  94. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/hub.rs +0 -0
  95. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  96. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/memory.rs +0 -0
  97. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  98. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/pace.rs +0 -0
  99. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  100. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
  101. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/pull.rs +0 -0
  102. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
  103. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/speculate.rs +0 -0
  104. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/transport/tests.rs +0 -0
  105. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/src/vectors.rs +0 -0
  106. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fd_headroom.rs +0 -0
  107. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  108. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fit.rs +0 -0
  109. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  110. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  111. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  112. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  113. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  114. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  115. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  116. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
  117. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
  118. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  119. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/home.rs +0 -0
  120. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  121. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  122. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/liveness.rs +0 -0
  123. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/read_plan.rs +0 -0
  124. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  125. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  126. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  127. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  128. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  129. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  130. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  131. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  132. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/source_download.rs +0 -0
  133. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  134. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
  135. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
  136. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/staging.rs +0 -0
  137. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
  138. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  139. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/transfer_memory.rs +0 -0
  140. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/trust_index.rs +0 -0
  141. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/version.rs +0 -0
  142. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-core/tests/write_through.rs +0 -0
  143. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-py/Cargo.toml +0 -0
  144. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-py/src/docs.rs +0 -0
  145. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-py/src/errors.rs +0 -0
  146. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-py/src/ingest.rs +0 -0
  147. {tensorfs-0.3.61 → tensorfs-0.3.63}/crates/tensorfs-py/src/lib.rs +0 -0
  148. {tensorfs-0.3.61 → tensorfs-0.3.63}/python/tensorfs/__init__.py +0 -0
  149. {tensorfs-0.3.61 → tensorfs-0.3.63}/python/tensorfs/_derived_channel.py +0 -0
  150. {tensorfs-0.3.61 → tensorfs-0.3.63}/python/tensorfs/_ext.pyi +0 -0
  151. {tensorfs-0.3.61 → tensorfs-0.3.63}/python/tensorfs/_source_channel.py +0 -0
  152. {tensorfs-0.3.61 → tensorfs-0.3.63}/python/tensorfs/_tensors.py +0 -0
  153. {tensorfs-0.3.61 → tensorfs-0.3.63}/python/tensorfs/derived.py +0 -0
  154. {tensorfs-0.3.61 → tensorfs-0.3.63}/python/tensorfs/errors.py +0 -0
  155. {tensorfs-0.3.61 → tensorfs-0.3.63}/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.61"
442
+ version = "0.3.63"
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.61"
457
+ version = "0.3.63"
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.61"
6
+ version = "0.3.63"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.61
3
+ Version: 0.3.63
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
@@ -11,7 +11,7 @@ use crate::checkpoint;
11
11
  use crate::err::{refuse, Code, Refusal, Result};
12
12
  use crate::header::Asset;
13
13
  use crate::ids::ObjectRef;
14
- use crate::manifest::{Entry, Manifest};
14
+ use crate::manifest::Manifest;
15
15
  use crate::store::Store;
16
16
 
17
17
  #[derive(Debug, Clone, PartialEq, Eq)]
@@ -42,13 +42,7 @@ fn source_object(manifest: &Manifest, path: &str) -> Result<ObjectRef> {
42
42
  .entries()
43
43
  .iter()
44
44
  .find_map(|(candidate, entry)| {
45
- (candidate == path).then(|| match entry {
46
- Entry::File(object) => Ok(object.clone()),
47
- Entry::CozyTensors(_) => Err(Refusal {
48
- code: Code::WRONG_TYPE,
49
- detail: format!("asset source {path:?} is a CozyTensors header, not a file"),
50
- }),
51
- })
45
+ (candidate == path).then(|| entry.materializable(path).cloned())
52
46
  })
53
47
  .unwrap_or_else(|| {
54
48
  Err(Refusal {
@@ -187,6 +181,7 @@ mod tests {
187
181
  use crate::checkpoint;
188
182
  use crate::header::{Body, Header, Part, Tensor};
189
183
  use crate::manifest::Draft;
184
+ use crate::manifest::Entry;
190
185
  use crate::store::Fault;
191
186
  use crate::{dtype::Dtype, registry};
192
187
 
@@ -2207,16 +2207,17 @@ fn build_corpus() -> TfsResult<(Vec<Case>, Vec<EncodingSpec>)> {
2207
2207
  "carrier no longer equals the logical dtype",
2208
2208
  ));
2209
2209
 
2210
- // --- location-typed Manifest reds
2210
+ // A kind a newer TensorFS wrote is opaque here: it parses, and only materializing refuses.
2211
2211
  let manifest_bytes = snap2.canonical_bytes();
2212
- cases.push(red_case(
2213
- "manifest-red",
2214
- "entry-kind-symlink",
2212
+ cases.push(ok_case(
2213
+ "manifest",
2214
+ "entry-kind-future",
2215
2215
  "manifest",
2216
2216
  sub(&manifest_bytes, "\"kind\":\"file\"", "\"kind\":\"symlink\""),
2217
- "UNKNOWN_FIELD",
2218
- "only file and cozytensors entries exist",
2217
+ "an entry kind from a newer TensorFS is carried opaquely",
2219
2218
  ));
2219
+
2220
+ // --- location-typed Manifest reds
2220
2221
  let mut draft = Draft::of(&snap);
2221
2222
  draft.entries.clear();
2222
2223
  cases.push(red_case(
@@ -2259,6 +2260,7 @@ fn build_corpus() -> TfsResult<(Vec<Case>, Vec<EncodingSpec>)> {
2259
2260
  // --- the border's own documents (tfs-003)
2260
2261
  let g = "ingest";
2261
2262
  let session = tensorfs_core::ingest::transaction::IngestSession {
2263
+ tensorfs: None,
2262
2264
  session: "e3b0c44298fc1c14".to_string(),
2263
2265
  tenant: "acme".to_string(),
2264
2266
  candidates: vec![ObjectRef::of(b"a candidate header")],
@@ -9,7 +9,7 @@ use tensorfs_core::err::{Code, Refusal};
9
9
  use tensorfs_core::gc;
10
10
  use tensorfs_core::header::{Body, Header};
11
11
  use tensorfs_core::ids::{hex64, prefixed, Doc, ObjectRef};
12
- use tensorfs_core::manifest::{Entry, Manifest};
12
+ use tensorfs_core::manifest::Manifest;
13
13
  use tensorfs_core::reclaim;
14
14
  use tensorfs_core::repository::{self, Mutation, Repository};
15
15
  use tensorfs_core::storage::{
@@ -738,13 +738,7 @@ pub fn cmd_manifest_inspect(path: &Path, flags: &Flags) -> ExitCode {
738
738
  .iter()
739
739
  .map(|(path, entry)| {
740
740
  tensorfs_core::canon::write(&Value::obj(vec![
741
- (
742
- "kind",
743
- Value::str(match entry {
744
- Entry::File(_) => "file",
745
- Entry::CozyTensors(_) => "cozytensors",
746
- }),
747
- ),
741
+ ("kind", Value::str(entry.kind())),
748
742
  ("length", Value::uint(entry.blob().length)),
749
743
  ("path", Value::str(path.clone())),
750
744
  ("sha256", Value::str(entry.blob().sha256.clone())),
@@ -855,7 +849,7 @@ fn closure_refs(
855
849
  // Runtime roles precede ordinary snapshot paths so an aliased README/sample path cannot
856
850
  // relabel a required config, model asset, or tensor part as an ignorable file.
857
851
  for (_, entry) in manifest.entries() {
858
- if let Entry::File(object) = entry {
852
+ if let Some(object) = entry.content() {
859
853
  insert(&mut refs, "file", object)?;
860
854
  }
861
855
  }
@@ -1206,7 +1200,7 @@ pub fn cmd_reclaim_run(root: &Path, flags: &Flags) -> ExitCode {
1206
1200
  mod tests {
1207
1201
  use super::*;
1208
1202
  use std::collections::HashSet;
1209
- use tensorfs_core::manifest::Draft;
1203
+ use tensorfs_core::manifest::{Draft, Entry};
1210
1204
 
1211
1205
  #[test]
1212
1206
  fn inspect_closure_preserves_runtime_roles_over_aliased_files() {
@@ -1246,10 +1240,7 @@ mod tests {
1246
1240
  let file_ids: HashSet<&str> = manifest
1247
1241
  .entries()
1248
1242
  .iter()
1249
- .filter_map(|(_, entry)| match entry {
1250
- Entry::File(object) => Some(object.sha256.as_str()),
1251
- Entry::CozyTensors(_) => None,
1252
- })
1243
+ .filter_map(|(_, entry)| entry.content().map(|object| object.sha256.as_str()))
1253
1244
  .collect();
1254
1245
 
1255
1246
  assert!(header.assets.iter().any(|(_, asset)| asset
@@ -1268,11 +1259,11 @@ mod tests {
1268
1259
  .filter(|(kind, _)| *kind != "file")
1269
1260
  .map(|(_, object)| object.sha256.as_str())
1270
1261
  .collect();
1271
- assert!(manifest.entries().iter().any(|(_, entry)| match entry {
1272
- Entry::File(object) => {
1262
+ assert!(manifest
1263
+ .entries()
1264
+ .iter()
1265
+ .any(|(_, entry)| entry.content().is_some_and(|object| {
1273
1266
  !runtime_ids.contains(object.sha256.as_str()) && refs[&object.sha256].0 == "file"
1274
- }
1275
- Entry::CozyTensors(_) => false,
1276
- }));
1267
+ })));
1277
1268
  }
1278
1269
  }
@@ -104,7 +104,7 @@ fn referenced_blobs(header: &Header, draft: &Draft) -> Result<BTreeMap<String, u
104
104
  }
105
105
  }
106
106
  for (_, entry) in &draft.entries {
107
- if let Entry::File(object) = entry {
107
+ if let Some(object) = entry.content() {
108
108
  add_ref(&mut refs, object, "manifest file")?;
109
109
  }
110
110
  }
@@ -257,6 +257,9 @@ pub fn cmd_show(root: &Path, hex: &str) -> ExitCode {
257
257
  blob.id(),
258
258
  blob.length
259
259
  ),
260
+ Entry::Other { kind, blob } => {
261
+ println!(" {kind:<12} {path} {} ({} B)", blob.id(), blob.length)
262
+ }
260
263
  }
261
264
  }
262
265
  ExitCode::SUCCESS
@@ -456,6 +456,31 @@ impl<'a> Fields<'a> {
456
456
  }
457
457
  }
458
458
 
459
+ /// End a record whose unread field could keep bytes alive (a GC root): refuse, naming the
460
+ /// newer TensorFS that wrote it, rather than drop what that version may rely on.
461
+ pub fn done_written_by(self, writer: Option<&str>) -> Result<()> {
462
+ let Some((k, _)) = self.left.first() else {
463
+ return Ok(());
464
+ };
465
+ let by = writer.map_or_else(
466
+ || "a newer TensorFS".to_string(),
467
+ |writer| format!("TensorFS {writer}"),
468
+ );
469
+ let upgrade = writer.map_or_else(
470
+ || "upgrade TensorFS".to_string(),
471
+ |writer| format!("upgrade to tensorfs>={writer}"),
472
+ );
473
+ refuse(
474
+ Code::UNKNOWN_FIELD,
475
+ format!(
476
+ "{}: field {k:?} was written by {by}; this is TensorFS {} — {upgrade}. It is \
477
+ refused rather than dropped because it may keep bytes alive",
478
+ self.what,
479
+ crate::VERSION
480
+ ),
481
+ )
482
+ }
483
+
459
484
  /// End a document that tolerates additive fields, keeping them for re-emission.
460
485
  pub fn rest(self) -> Extra {
461
486
  Extra(
@@ -523,17 +523,14 @@ pub fn walk(store: &Store, snap: &Manifest) -> Result<Walk> {
523
523
  .map(|r| (r.obj.sha256.clone(), r.obj.length))
524
524
  .collect();
525
525
  for (path, e) in snap.entries() {
526
- match e {
527
- crate::manifest::Entry::File(o) => {
528
- if !accounted.contains(&(o.sha256.clone(), o.length)) {
529
- w.siblings.push(path.clone());
530
- w.objects.push(Reached {
531
- kind: "asset",
532
- obj: o.clone(),
533
- });
534
- }
526
+ if let Some(o) = e.content() {
527
+ if !accounted.contains(&(o.sha256.clone(), o.length)) {
528
+ w.siblings.push(path.clone());
529
+ w.objects.push(Reached {
530
+ kind: "asset",
531
+ obj: o.clone(),
532
+ });
535
533
  }
536
- crate::manifest::Entry::CozyTensors(_) => {}
537
534
  }
538
535
  }
539
536
  Ok(w)
@@ -22,6 +22,8 @@ pub struct CheckpointRoot {
22
22
  pub repository: RepositoryName,
23
23
  pub manifest: ObjectRef,
24
24
  pub released: bool,
25
+ /// The newest TensorFS that wrote this root.
26
+ pub tensorfs: Option<String>,
25
27
  }
26
28
 
27
29
  impl Plain for CheckpointRoot {
@@ -36,11 +38,18 @@ impl Plain for CheckpointRoot {
36
38
  Value::Bool(value) => *value,
37
39
  _ => return refuse(Code::WRONG_TYPE, "checkpoint root release must be boolean"),
38
40
  };
39
- fields.done()?;
41
+ let tensorfs = fields
42
+ .opt("tensorfs")
43
+ .map(|value| {
44
+ crate::canon::as_str("CheckpointRoot", "tensorfs", value).map(str::to_string)
45
+ })
46
+ .transpose()?;
47
+ fields.done_written_by(tensorfs.as_deref())?;
40
48
  if manifest.length == 0 || manifest.length > crate::limits::INT_MAX as u64 {
41
49
  return refuse(Code::LENGTH_MISMATCH, "checkpoint root manifest is empty");
42
50
  }
43
51
  Ok(Self {
52
+ tensorfs,
44
53
  owner,
45
54
  repository,
46
55
  manifest,
@@ -49,7 +58,7 @@ impl Plain for CheckpointRoot {
49
58
  }
50
59
 
51
60
  fn to_value(&self) -> Value {
52
- Value::obj(vec![
61
+ let mut fields = vec![
53
62
  ("owner", Value::str(self.owner.clone())),
54
63
  (
55
64
  "repository",
@@ -57,7 +66,11 @@ impl Plain for CheckpointRoot {
57
66
  ),
58
67
  ("manifest", self.manifest.to_value()),
59
68
  ("released", Value::Bool(self.released)),
60
- ])
69
+ ];
70
+ if let Some(tensorfs) = &self.tensorfs {
71
+ fields.push(("tensorfs", Value::str(tensorfs.clone())));
72
+ }
73
+ Value::obj(fields)
61
74
  }
62
75
  }
63
76
 
@@ -126,6 +139,10 @@ fn lock(store: &Store, owner: &str) -> Result<File> {
126
139
  }
127
140
 
128
141
  fn write(store: &Store, root: &CheckpointRoot) -> Result<()> {
142
+ let root = &CheckpointRoot {
143
+ tensorfs: Some(crate::VERSION.into()),
144
+ ..root.clone()
145
+ };
129
146
  let path = path(store, &root.owner)?;
130
147
  let temporary = path.with_extension(format!("tmp-{}", crate::meta::now_nanos_unique()));
131
148
  let result = (|| {
@@ -159,6 +176,7 @@ fn identity(owner: &str, source: &str, manifest: ObjectRef) -> Result<Checkpoint
159
176
  return refuse(Code::LENGTH_MISMATCH, "checkpoint root manifest is empty");
160
177
  }
161
178
  Ok(CheckpointRoot {
179
+ tensorfs: Some(crate::VERSION.into()),
162
180
  owner: prefixed("checkpoint root owner", owner)?,
163
181
  repository: repository(source)?,
164
182
  manifest,
@@ -191,7 +191,7 @@ fn ordinary_companions_survive_derivation_without_copying_tensor_backing_files()
191
191
  .push(("LICENSE".into(), b"explicit licence".to_vec()));
192
192
  assert_eq!(
193
193
  companion_files(&d, &sources).unwrap()["LICENSE"],
194
- ObjectRef::of(b"explicit licence")
194
+ Entry::File(ObjectRef::of(b"explicit licence"))
195
195
  );
196
196
  std::fs::remove_dir_all(store.root()).unwrap();
197
197
  }
@@ -0,0 +1,169 @@
1
+ //! Accepted parts are rows of their own: accepting one never reads or rewrites the others.
2
+
3
+ use super::*;
4
+ use rusqlite::params;
5
+
6
+ fn fixture(label: &str) -> (Store, Meta, String, Declaration) {
7
+ let root = std::env::temp_dir().join(format!(
8
+ "tensorfs-part-rows-{label}-{}",
9
+ crate::meta::now_nanos_unique()
10
+ ));
11
+ let store = Store::init(&root).unwrap();
12
+ let meta = Meta::open(&store).unwrap();
13
+ let mut declaration = super::tests::created_declaration();
14
+ let prototype = declaration.components[0].add[0].clone();
15
+ declaration.components[0].add = (0..3)
16
+ .map(|index| {
17
+ let mut tensor = prototype.clone();
18
+ tensor.key = format!("weight_{index}");
19
+ tensor.shape = vec![128];
20
+ tensor.parts[0].shape = vec![128];
21
+ tensor
22
+ })
23
+ .collect();
24
+ declaration.order = (0..3)
25
+ .map(|index| ("model".into(), format!("weight_{index}")))
26
+ .collect();
27
+ declaration.max_new_bytes = 3 * 512;
28
+ (
29
+ store,
30
+ meta,
31
+ format!("sha256:{}", "7a".repeat(32)),
32
+ declaration,
33
+ )
34
+ }
35
+
36
+ fn catalog(store: &Store) -> rusqlite::Connection {
37
+ rusqlite::Connection::open(crate::catalog::Catalog::path(store.root())).unwrap()
38
+ }
39
+
40
+ fn add(store: &Store, meta: &Meta, id: &str, writer: &Begin, key: &str, byte: u8) -> Result<()> {
41
+ add_indexed_part(
42
+ store,
43
+ meta,
44
+ id,
45
+ 1,
46
+ &writer.additions,
47
+ ("model", key, "value"),
48
+ &mut [byte; 512].as_slice(),
49
+ )
50
+ .map(|_| ())
51
+ }
52
+
53
+ fn stored_rows(store: &Store, id: &str) -> (usize, Option<i64>, i64) {
54
+ let connection = catalog(store);
55
+ let (bytes, open): (Vec<u8>, Option<i64>) = connection
56
+ .query_row(
57
+ "SELECT bytes,open_session FROM tensorfs_derived_transactions WHERE id=?1",
58
+ [id],
59
+ |row| Ok((row.get(0)?, row.get(1)?)),
60
+ )
61
+ .unwrap();
62
+ let value = crate::canon::parse_canonical(&bytes, limits::DOC_MAX_BYTES).unwrap();
63
+ let inline = TransactionRow::from_value(&value)
64
+ .unwrap()
65
+ .added_parts
66
+ .len();
67
+ let parts = connection
68
+ .query_row(
69
+ "SELECT count(*) FROM tensorfs_derived_parts WHERE id=?1",
70
+ [id],
71
+ |row| row.get(0),
72
+ )
73
+ .unwrap();
74
+ (inline, open, parts)
75
+ }
76
+
77
+ #[test]
78
+ fn parts_are_rows_that_resume_refuse_conflicts_and_stay_retained() {
79
+ let (store, meta, id, declaration) = fixture("rows");
80
+ let writer = begin(&store, &meta, &id, 1, declaration.clone(), None).unwrap();
81
+ add(&store, &meta, &id, &writer, "weight_0", 1).unwrap();
82
+ add(&store, &meta, &id, &writer, "weight_1", 2).unwrap();
83
+ // The row itself names no part; each part is one row; the writer's session is indexed.
84
+ assert_eq!(stored_rows(&store, &id), (0, Some(1), 2));
85
+ // The same bytes again are the same accepted part; other bytes conflict.
86
+ add(&store, &meta, &id, &writer, "weight_0", 1).unwrap();
87
+ assert_eq!(stored_rows(&store, &id).2, 2);
88
+ assert_eq!(
89
+ add(&store, &meta, &id, &writer, "weight_0", 9)
90
+ .unwrap_err()
91
+ .code,
92
+ Code::TRANSACTION_CONFLICT
93
+ );
94
+ // A fenced writer cannot add. Once it is gone, GC keeps its accepted bytes through
95
+ // the append-only journal alone, and its successor resumes with both parts.
96
+ fence(&meta, &id, 1).unwrap();
97
+ assert_eq!(
98
+ add(&store, &meta, &id, &writer, "weight_2", 3)
99
+ .unwrap_err()
100
+ .code,
101
+ Code::WRITER_FENCED
102
+ );
103
+ release_guards(&meta, Some(writer.writer_hold), writer.source_leases);
104
+ crate::gc::collect(store.root(), false).unwrap();
105
+ assert!(store.contains(&ObjectRef::of(&[1u8; 512]).sha256));
106
+ assert!(store.contains(&ObjectRef::of(&[2u8; 512]).sha256));
107
+ let resumed = begin(&store, &meta, &id, 2, declaration, None).unwrap();
108
+ assert_eq!(completed(&meta, &id, 2).unwrap().0.len(), 2);
109
+ add_indexed_part(
110
+ &store,
111
+ &meta,
112
+ &id,
113
+ 2,
114
+ &resumed.additions,
115
+ ("model", "weight_2", "value"),
116
+ &mut [3u8; 512].as_slice(),
117
+ )
118
+ .unwrap();
119
+ let receipt = commit(&store, &meta, &id, 2).unwrap();
120
+ assert_eq!(receipt.added_objects.len(), 3);
121
+ assert_eq!(stored_rows(&store, &id).1, None);
122
+ fence(&meta, &id, 2).unwrap();
123
+ release_guards(&meta, Some(resumed.writer_hold), resumed.source_leases);
124
+ std::fs::remove_dir_all(store.root()).unwrap();
125
+ }
126
+
127
+ #[test]
128
+ fn an_older_builds_inline_row_and_a_torn_journal_line_are_read() {
129
+ let (store, meta, id, declaration) = fixture("legacy");
130
+ let writer = begin(&store, &meta, &id, 1, declaration, None).unwrap();
131
+ add(&store, &meta, &id, &writer, "weight_0", 1).unwrap();
132
+ // An older build rewrites the row with its parts inline and no open session.
133
+ let row = meta.derived_row(&id).unwrap().unwrap();
134
+ let connection = catalog(&store);
135
+ connection
136
+ .execute("DELETE FROM tensorfs_derived_parts WHERE id=?1", [&id])
137
+ .unwrap();
138
+ connection
139
+ .execute(
140
+ "UPDATE tensorfs_derived_transactions SET bytes=?2, open_session=NULL WHERE id=?1",
141
+ params![id, crate::canon::write(&row.to_value())],
142
+ )
143
+ .unwrap();
144
+ // A writer that died mid-append left a torn journal line.
145
+ let journal = store
146
+ .root()
147
+ .join("roots/derived")
148
+ .join(format!("{}.parts", id.trim_start_matches("sha256:")));
149
+ let mut torn = std::fs::read(&journal).unwrap();
150
+ torn.extend_from_slice(b"[{\"length\":512,\"sha");
151
+ std::fs::write(&journal, torn).unwrap();
152
+
153
+ assert_eq!(
154
+ add(&store, &meta, &id, &writer, "weight_0", 9)
155
+ .unwrap_err()
156
+ .code,
157
+ Code::TRANSACTION_CONFLICT
158
+ );
159
+ add(&store, &meta, &id, &writer, "weight_1", 2).unwrap();
160
+ assert_eq!(completed(&meta, &id, 1).unwrap().0.len(), 2);
161
+ fence(&meta, &id, 1).unwrap();
162
+ release_guards(&meta, Some(writer.writer_hold), writer.source_leases);
163
+ crate::gc::collect(store.root(), false).unwrap();
164
+ assert!(store.contains(&ObjectRef::of(&[1u8; 512]).sha256));
165
+ assert!(store.contains(&ObjectRef::of(&[2u8; 512]).sha256));
166
+ abandon(&store, &meta, &id).unwrap();
167
+ assert!(!journal.exists());
168
+ std::fs::remove_dir_all(store.root()).unwrap();
169
+ }
@@ -984,11 +984,12 @@ fn profile_derived_transaction_scaling() {
984
984
  for index in 0..writes {
985
985
  let key = format!("weight_{index:04}");
986
986
  let started = Instant::now();
987
- add_part(
987
+ add_indexed_part(
988
988
  &store,
989
989
  &meta,
990
990
  &id,
991
991
  1,
992
+ &writer.additions,
992
993
  ("model", &key, "value"),
993
994
  &mut [index as u8; 16].as_slice(),
994
995
  )
@@ -22,6 +22,8 @@ pub struct RetainedResult {
22
22
  pub receipt_digest: String,
23
23
  pub manifest: Option<ObjectRef>,
24
24
  pub released: bool,
25
+ /// The newest TensorFS that wrote this root.
26
+ pub tensorfs: Option<String>,
25
27
  }
26
28
 
27
29
  impl Plain for RetainedResult {
@@ -40,11 +42,18 @@ impl Plain for RetainedResult {
40
42
  Value::Bool(value) => *value,
41
43
  _ => return refuse(Code::WRONG_TYPE, "retained result release must be boolean"),
42
44
  };
43
- f.done()?;
45
+ let tensorfs = f
46
+ .opt("tensorfs")
47
+ .map(|value| {
48
+ crate::canon::as_str("RetainedResult", "tensorfs", value).map(str::to_string)
49
+ })
50
+ .transpose()?;
51
+ f.done_written_by(tensorfs.as_deref())?;
44
52
  if !released && manifest.is_none() {
45
53
  return refuse(Code::MISSING_FIELD, "held result has no manifest");
46
54
  }
47
55
  Ok(Self {
56
+ tensorfs,
48
57
  retention_id,
49
58
  transaction_id,
50
59
  receipt_digest,
@@ -63,6 +72,9 @@ impl Plain for RetainedResult {
63
72
  if let Some(manifest) = &self.manifest {
64
73
  fields.push(("manifest", manifest.to_value()));
65
74
  }
75
+ if let Some(tensorfs) = &self.tensorfs {
76
+ fields.push(("tensorfs", Value::str(tensorfs.clone())));
77
+ }
66
78
  Value::obj(fields)
67
79
  }
68
80
  }
@@ -104,6 +116,10 @@ fn read(store: &Store, id: &str) -> Result<Option<RetainedResult>> {
104
116
  }
105
117
 
106
118
  fn write(store: &Store, root: &RetainedResult) -> Result<()> {
119
+ let root = &RetainedResult {
120
+ tensorfs: Some(crate::VERSION.into()),
121
+ ..root.clone()
122
+ };
107
123
  let path = path(store, &root.retention_id)?;
108
124
  fs::create_dir_all(directory(store)).map_err(io)?;
109
125
  let temp = path.with_extension(format!("tmp-{}", crate::meta::now_nanos_unique()));
@@ -128,6 +144,7 @@ fn write(store: &Store, root: &RetainedResult) -> Result<()> {
128
144
 
129
145
  fn identity(transaction: &str, receipt: &str, id: &str) -> Result<RetainedResult> {
130
146
  Ok(RetainedResult {
147
+ tensorfs: Some(crate::VERSION.into()),
131
148
  retention_id: prefixed("retention", id)?,
132
149
  transaction_id: prefixed("transaction", transaction)?,
133
150
  receipt_digest: prefixed("receipt", receipt)?,