tensorfs 0.3.65__tar.gz → 0.3.67__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 (158) hide show
  1. {tensorfs-0.3.65 → tensorfs-0.3.67}/Cargo.lock +2 -2
  2. {tensorfs-0.3.65 → tensorfs-0.3.67}/Cargo.toml +1 -1
  3. {tensorfs-0.3.65 → tensorfs-0.3.67}/PKG-INFO +1 -1
  4. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/fetch.rs +7 -3
  5. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/rd.rs +9 -5
  6. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/catalog.rs +36 -11
  7. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/preflight.rs +20 -0
  8. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/source.rs +87 -17
  9. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/read.rs +58 -57
  10. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/store.rs +13 -11
  11. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/tests.rs +122 -89
  12. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/anima_hf_profile.rs +103 -0
  13. tensorfs-0.3.67/crates/tensorfs-core/tests/civitai_sdxl_profile.rs +84 -0
  14. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fd_budget.rs +32 -19
  15. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/read_plan.rs +1 -1
  16. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-py/src/ingest.rs +33 -9
  17. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-py/src/lib.rs +1 -0
  18. {tensorfs-0.3.65 → tensorfs-0.3.67}/pyproject.toml +1 -1
  19. {tensorfs-0.3.65 → tensorfs-0.3.67}/python/tensorfs/__init__.py +2 -0
  20. {tensorfs-0.3.65 → tensorfs-0.3.67}/python/tensorfs/_ext.pyi +6 -0
  21. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/Cargo.toml +0 -0
  22. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/build.rs +0 -0
  23. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  24. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  25. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  26. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  27. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/asset_update.rs +0 -0
  28. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/b64.rs +0 -0
  29. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  30. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  31. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
  32. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
  33. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
  34. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  35. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  36. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/cache_roots.rs +0 -0
  37. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/canon.rs +0 -0
  38. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/capability.rs +0 -0
  39. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/cbor.rs +0 -0
  40. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  41. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
  42. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/corpus.rs +0 -0
  43. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  44. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/companion_tests.rs +0 -0
  45. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  46. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  47. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/part_rows_tests.rs +0 -0
  48. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  49. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
  50. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/retention.rs +0 -0
  51. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived/roots.rs +0 -0
  52. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/derived.rs +0 -0
  53. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/descriptors.rs +0 -0
  54. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/dtype.rs +0 -0
  55. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/durability.rs +0 -0
  56. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/err.rs +0 -0
  57. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/fetch.rs +0 -0
  58. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/fill.rs +0 -0
  59. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/fit.rs +0 -0
  60. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/gc.rs +0 -0
  61. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/header.rs +0 -0
  62. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/home.rs +0 -0
  63. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ids.rs +0 -0
  64. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  65. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
  66. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/custody.rs +0 -0
  67. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
  68. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  69. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
  70. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  71. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  72. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  73. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
  74. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/jcs.rs +0 -0
  75. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/lib.rs +0 -0
  76. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/limits.rs +0 -0
  77. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/machine.rs +0 -0
  78. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/manifest.rs +0 -0
  79. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/meta.rs +0 -0
  80. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/project.rs +0 -0
  81. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/providers.rs +0 -0
  82. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/receipt.rs +0 -0
  83. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/reclaim.rs +0 -0
  84. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/registry.rs +0 -0
  85. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/relation.rs +0 -0
  86. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/repo_cache/identity.rs +0 -0
  87. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/repo_cache.rs +0 -0
  88. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/repository.rs +0 -0
  89. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/sha256.rs +0 -0
  90. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/source_artifact.rs +0 -0
  91. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/spec.rs +0 -0
  92. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/staging.rs +0 -0
  93. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/stats.rs +0 -0
  94. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/storage.rs +0 -0
  95. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/api.rs +0 -0
  96. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/http.rs +0 -0
  97. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +0 -0
  98. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/hub.rs +0 -0
  99. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  100. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/memory.rs +0 -0
  101. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  102. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/pace.rs +0 -0
  103. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  104. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
  105. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/pull.rs +0 -0
  106. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/push.rs +0 -0
  107. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
  108. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/transport/speculate.rs +0 -0
  109. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/src/vectors.rs +0 -0
  110. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/additive_fields.rs +0 -0
  111. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
  112. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fd_headroom.rs +0 -0
  113. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  114. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fit.rs +0 -0
  115. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  116. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  117. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  118. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  119. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  120. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  121. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  122. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
  123. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
  124. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  125. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/home.rs +0 -0
  126. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  127. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  128. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/liveness.rs +0 -0
  129. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/qwen_image21_profile.rs +0 -0
  130. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  131. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  132. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  133. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  134. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  135. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  136. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  137. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  138. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/source_download.rs +0 -0
  139. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  140. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
  141. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
  142. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/staging.rs +0 -0
  143. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
  144. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  145. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/transfer_memory.rs +0 -0
  146. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/trust_index.rs +0 -0
  147. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/version.rs +0 -0
  148. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-core/tests/write_through.rs +0 -0
  149. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-py/Cargo.toml +0 -0
  150. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-py/src/docs.rs +0 -0
  151. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-py/src/errors.rs +0 -0
  152. {tensorfs-0.3.65 → tensorfs-0.3.67}/crates/tensorfs-py/src/store.rs +0 -0
  153. {tensorfs-0.3.65 → tensorfs-0.3.67}/python/tensorfs/_derived_channel.py +0 -0
  154. {tensorfs-0.3.65 → tensorfs-0.3.67}/python/tensorfs/_source_channel.py +0 -0
  155. {tensorfs-0.3.65 → tensorfs-0.3.67}/python/tensorfs/_tensors.py +0 -0
  156. {tensorfs-0.3.65 → tensorfs-0.3.67}/python/tensorfs/derived.py +0 -0
  157. {tensorfs-0.3.65 → tensorfs-0.3.67}/python/tensorfs/errors.py +0 -0
  158. {tensorfs-0.3.65 → tensorfs-0.3.67}/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.65"
442
+ version = "0.3.67"
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.65"
457
+ version = "0.3.67"
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.65"
6
+ version = "0.3.67"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.65
3
+ Version: 0.3.67
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
@@ -1358,6 +1358,7 @@ struct PullProgress {
1358
1358
  struct PullProgressState {
1359
1359
  total_bytes: u64,
1360
1360
  held_bytes: u64,
1361
+ wanted_objects: u64,
1361
1362
  origin_bytes: u64,
1362
1363
  cache_bytes: u64,
1363
1364
  origin_objects: u64,
@@ -1371,6 +1372,7 @@ impl PullProgress {
1371
1372
  state: Mutex::new(PullProgressState {
1372
1373
  total_bytes: 0,
1373
1374
  held_bytes: 0,
1375
+ wanted_objects: 0,
1374
1376
  origin_bytes: 0,
1375
1377
  cache_bytes: 0,
1376
1378
  origin_objects: 0,
@@ -1385,6 +1387,7 @@ impl PullProgress {
1385
1387
  let mut state = self.state.lock().unwrap();
1386
1388
  state.total_bytes = plan.declared_bytes();
1387
1389
  state.held_bytes = plan.held_bytes();
1390
+ state.wanted_objects = plan.wanted.len() as u64;
1388
1391
  state.last = Some(Instant::now());
1389
1392
  print_progress(&state);
1390
1393
  }
@@ -1447,11 +1450,12 @@ impl PullProgress {
1447
1450
  ///
1448
1451
  /// Every counter is always present, including a zero. A consumer that reads an absent key
1449
1452
  /// as zero cannot tell a cache that answered nothing from a build that never reported the
1450
- /// door at all, and those are the two states worth telling apart.
1453
+ /// door at all, and those are the two states worth telling apart. `wanted_objects` is the
1454
+ /// plan's count of objects to land; a pod host sizes this pull's storage reservation from it.
1451
1455
  fn print_progress(state: &PullProgressState) {
1452
1456
  eprintln!(
1453
- "{{\"event\":\"pull.progress\",\"total_bytes\":{},\"held_bytes\":{},\"origin_bytes\":{},\"cache_bytes\":{},\"origin_objects\":{},\"cache_objects\":{}}}",
1454
- state.total_bytes, state.held_bytes, state.origin_bytes, state.cache_bytes,
1457
+ "{{\"event\":\"pull.progress\",\"total_bytes\":{},\"held_bytes\":{},\"wanted_objects\":{},\"origin_bytes\":{},\"cache_bytes\":{},\"origin_objects\":{},\"cache_objects\":{}}}",
1458
+ state.total_bytes, state.held_bytes, state.wanted_objects, state.origin_bytes, state.cache_bytes,
1455
1459
  state.origin_objects, state.cache_objects
1456
1460
  );
1457
1461
  }
@@ -133,9 +133,13 @@ pub fn cmd_plan(root: &Path, hex: &str, f: &Flags) -> ExitCode {
133
133
  let mut rev = construction_order(&h);
134
134
  rev.reverse();
135
135
  println!(
136
- "contract: reversed caller traversal refuses: {}",
136
+ "contract: reversed caller traversal plans in its own order: {}",
137
137
  match read::plan_for_traversal(&h, &rev, &all_components(&h), window) {
138
- Err(e) if e.code == Code::TRAVERSAL_ORDER_MISMATCH => "YES",
138
+ Ok(p)
139
+ if p.items.first().is_some_and(|it| it
140
+ .what
141
+ .starts_with(&format!("{}/{}#", rev[0].0, rev[0].1))) =>
142
+ "YES",
139
143
  _ => "NO — DEFECT",
140
144
  }
141
145
  );
@@ -991,7 +995,7 @@ pub fn cmd_arms(root: &Path, hex: &str) -> ExitCode {
991
995
  short.pop();
992
996
  a.arm(
993
997
  "traversal missing a tensor",
994
- Code::TRAVERSAL_ORDER_MISMATCH,
998
+ Code::TRAVERSAL_INCOMPLETE,
995
999
  read::plan_for_traversal(&h, &short, &all_components(&h), 1 << 20),
996
1000
  );
997
1001
 
@@ -1026,7 +1030,7 @@ pub fn cmd_arms(root: &Path, hex: &str) -> ExitCode {
1026
1030
  holed.pop();
1027
1031
  a.arm(
1028
1032
  "a tensor missing WITHIN a requested component",
1029
- Code::TRAVERSAL_ORDER_MISMATCH,
1033
+ Code::TRAVERSAL_INCOMPLETE,
1030
1034
  read::plan_for_traversal(&h, &holed, only_one, 1 << 20),
1031
1035
  );
1032
1036
  }
@@ -1042,7 +1046,7 @@ pub fn cmd_arms(root: &Path, hex: &str) -> ExitCode {
1042
1046
  }
1043
1047
  a.arm(
1044
1048
  "a tensor from a component the plan did not request",
1045
- Code::TRAVERSAL_ORDER_MISMATCH,
1049
+ Code::TRAVERSAL_INCOMPLETE,
1046
1050
  read::plan_for_traversal(&h, &smuggled, only_one, 1 << 20),
1047
1051
  );
1048
1052
 
@@ -62,6 +62,11 @@ fn sql(what: impl AsRef<str>, error: rusqlite::Error) -> Refusal {
62
62
  }
63
63
  }
64
64
 
65
+ /// Whether a catalog refusal is SQLite's busy timeout expiring behind another writer.
66
+ fn busy(refusal: &Refusal) -> bool {
67
+ refusal.code == Code::IO_FAILED && refusal.detail.ends_with("database is locked")
68
+ }
69
+
65
70
  fn io(what: impl AsRef<str>, error: std::io::Error) -> Refusal {
66
71
  crate::store::classify_io(what, error)
67
72
  }
@@ -115,7 +120,20 @@ fn connect(path: &Path) -> Result<Connection> {
115
120
  .entry(path.to_path_buf())
116
121
  .or_default() += 1;
117
122
  crate::stats::catalog_opened();
118
- let connection = Connection::open(path).map_err(|error| sql("open catalog", error))?;
123
+ let connection = Connection::open(path).map_err(|error| {
124
+ // SQLite reports a full descriptor table only as "unable to open"; the table
125
+ // itself answers whether that is what happened.
126
+ if crate::descriptors::available().is_some_and(|free| free == 0) {
127
+ crate::descriptors::exhausted(&std::io::Error::from_raw_os_error(
128
+ rustix::io::Errno::MFILE.raw_os_error(),
129
+ ));
130
+ return Refusal {
131
+ code: Code::FD_HEADROOM,
132
+ detail: format!("open catalog: no file descriptor ({error})"),
133
+ };
134
+ }
135
+ sql("open catalog", error)
136
+ })?;
119
137
  connection
120
138
  .busy_timeout(BUSY_TIMEOUT)
121
139
  .map_err(|error| sql("set catalog busy timeout", error))?;
@@ -372,6 +390,22 @@ impl Catalog {
372
390
  Ok(catalog)
373
391
  }
374
392
 
393
+ /// `open` for an object admission. A catalog busy past SQLite's timeout while this
394
+ /// process's own admission batches keep committing is a queue, not a failure: the
395
+ /// verified bytes wait for their turn instead of being thrown away and fetched again.
396
+ /// Busy with no batch committed in the meantime is another writer's lock, and refuses.
397
+ pub(crate) fn open_admitting(root: &Path) -> Result<Self> {
398
+ loop {
399
+ let committed = crate::stats::snapshot().admission_batches;
400
+ match Self::open(root) {
401
+ Err(refusal)
402
+ if busy(&refusal) && crate::stats::snapshot().admission_batches > committed => {
403
+ }
404
+ opened => return opened,
405
+ }
406
+ }
407
+ }
408
+
375
409
  pub fn open(root: &Path) -> Result<Self> {
376
410
  let path = Self::path(root);
377
411
  validated(&path)?;
@@ -1473,16 +1507,7 @@ impl WriterGuard {
1473
1507
  .read(true)
1474
1508
  .write(true)
1475
1509
  .open(&path)
1476
- .map_err(|error| {
1477
- if crate::descriptors::exhausted(&error) {
1478
- Refusal {
1479
- code: Code::FD_HEADROOM,
1480
- detail: format!("create writer marker: {error}"),
1481
- }
1482
- } else {
1483
- io("create writer marker", error)
1484
- }
1485
- })?;
1510
+ .map_err(|error| io("create writer marker", error))?;
1486
1511
  file.try_lock_exclusive()
1487
1512
  .map_err(|error| io("lock writer marker", error))?;
1488
1513
  Ok(Self {
@@ -239,6 +239,26 @@ fn profile_plan(plan: &SourcePlan) -> ProfilePlan {
239
239
  }
240
240
  }
241
241
 
242
+ /// The one reviewed profile these member heads plan under, chosen from their headers alone:
243
+ /// several matching profiles that plan identically are one answer; different plans refuse
244
+ /// `AMBIGUOUS_CLASSIFICATION` naming them, and none refuses `UNREGISTERED_FINGERPRINT`.
245
+ pub fn select_profile(
246
+ registry_locator: &str,
247
+ registry_bytes: &[u8],
248
+ heads: &[MemberHead],
249
+ scratch: &Path,
250
+ ) -> Result<String> {
251
+ let staged = stage(scratch, heads)?;
252
+ source::plan_source(
253
+ registry_locator,
254
+ registry_bytes,
255
+ staged.carriers(),
256
+ None,
257
+ None,
258
+ )
259
+ .map(|plan| plan.profile)
260
+ }
261
+
242
262
  /// Decide the conversion plan for every requested profile, from headers alone.
243
263
  ///
244
264
  /// One [`source::plan_source`] call per profile, over one carrier set — which is precisely
@@ -690,12 +690,6 @@ fn plan_source_prepared(
690
690
  }
691
691
  for assignments in profile_assignments(profile, &registry, carriers, &headers) {
692
692
  matches.push((profile, assignments));
693
- if matches.len() >= 2 {
694
- break;
695
- }
696
- }
697
- if matches.len() >= 2 {
698
- break;
699
693
  }
700
694
  }
701
695
  if matches.is_empty() {
@@ -704,20 +698,61 @@ fn plan_source_prepared(
704
698
  "carrier set matches no reviewed source profile",
705
699
  );
706
700
  }
707
- if matches.len() != 1 {
708
- let profiles: Vec<&str> = matches
701
+ // Every match is planned. Matches whose plans are identical (the same sources, order,
702
+ // target and session under different profile names) are one answer, named by the most
703
+ // specific profile; only genuinely different plans are ambiguous.
704
+ let mut planned = Vec::with_capacity(matches.len());
705
+ let mut first_error = None;
706
+ for (profile, assignments) in matches {
707
+ match plan_match(
708
+ profile,
709
+ assignments,
710
+ carriers,
711
+ &set,
712
+ &registry,
713
+ registry_locator,
714
+ registry_bytes,
715
+ spec_variant,
716
+ ) {
717
+ Ok(result) => planned.push((profile, result)),
718
+ Err(error) => {
719
+ first_error.get_or_insert(error);
720
+ }
721
+ }
722
+ }
723
+ if planned.is_empty() {
724
+ return Err(first_error.expect("every match was planned or refused"));
725
+ }
726
+ let same = |left: &SourcePlan, right: &SourcePlan| {
727
+ (
728
+ &left.session,
729
+ &left.construction_order,
730
+ &left.sources,
731
+ &left.target,
732
+ ) == (
733
+ &right.session,
734
+ &right.construction_order,
735
+ &right.sources,
736
+ &right.target,
737
+ )
738
+ };
739
+ if planned
740
+ .iter()
741
+ .any(|(_, (plan, _))| !same(plan, &planned[0].1 .0))
742
+ {
743
+ let mut profiles: Vec<&str> = planned
709
744
  .iter()
710
745
  .map(|(profile, _)| profile.name.as_str())
711
746
  .collect();
712
- let mut members: Vec<String> = matches
747
+ profiles.dedup();
748
+ let mut members: Vec<String> = planned
713
749
  .iter()
714
- .flat_map(|(_, assignments)| {
715
- assignments.iter().map(|assignment| {
716
- let carrier = &carriers[assignment.carrier];
717
- carrier
718
- .member
750
+ .flat_map(|(_, (plan, _))| {
751
+ plan.sources.iter().map(|source| {
752
+ source
753
+ .source_member
719
754
  .clone()
720
- .unwrap_or_else(|| carrier.path.display().to_string())
755
+ .unwrap_or_else(|| source.path.display().to_string())
721
756
  })
722
757
  })
723
758
  .collect();
@@ -731,7 +766,42 @@ fn plan_source_prepared(
731
766
  ),
732
767
  );
733
768
  }
734
- let (profile, mut assignments) = matches.remove(0);
769
+ planned.sort_by_key(|(profile, _)| {
770
+ (
771
+ std::cmp::Reverse(specificity(profile)),
772
+ profile.name.clone(),
773
+ )
774
+ });
775
+ let (_, (plan, prepared)) = planned.swap_remove(0);
776
+ Ok((plan, prepared))
777
+ }
778
+
779
+ /// How exactly a profile binds its components: provider members, then member prefixes.
780
+ fn specificity(profile: &SourceProfile) -> (usize, usize) {
781
+ let exact = profile
782
+ .components
783
+ .iter()
784
+ .filter(|component| component.source_member.is_some())
785
+ .count();
786
+ let prefixed = profile
787
+ .components
788
+ .iter()
789
+ .filter(|component| component.source_member_prefix.is_some())
790
+ .count();
791
+ (exact, prefixed)
792
+ }
793
+
794
+ #[allow(clippy::too_many_arguments)]
795
+ fn plan_match(
796
+ profile: &SourceProfile,
797
+ mut assignments: Vec<SourceAssignment>,
798
+ carriers: &[CarrierInput],
799
+ set: &CarrierSet,
800
+ registry: &FingerprintRegistry,
801
+ registry_locator: &str,
802
+ registry_bytes: &[u8],
803
+ spec_variant: Option<usize>,
804
+ ) -> Result<(SourcePlan, Prepared)> {
735
805
  assignments.sort_by(|left, right| left.component.cmp(&right.component));
736
806
  let sources: Vec<(String, CarrierInput)> = assignments
737
807
  .iter()
@@ -762,7 +832,7 @@ fn plan_source_prepared(
762
832
  .map(|key| (component.component.clone(), key.clone()))
763
833
  })
764
834
  .collect();
765
- let mut prepared = prepare_with_registry(&target, &sources, &set, &registry, spec_variant)?;
835
+ let mut prepared = prepare_with_registry(&target, &sources, set, registry, spec_variant)?;
766
836
  prepared.plan.apply_order(&order)?;
767
837
  prepared.plan.check_converts_something()?;
768
838
  let plan = SourcePlan {
@@ -24,7 +24,7 @@ use std::time::Instant;
24
24
 
25
25
  use crate::checkpoint;
26
26
  use crate::err::{refuse, Code, Refusal, Result};
27
- use crate::header::{Asset, Body, Header, Located, Part};
27
+ use crate::header::{Asset, Body, Header, Located, Part, Tensor};
28
28
  use crate::ids::ObjectRef;
29
29
  use crate::manifest::Manifest;
30
30
  use crate::meta::{Hold, Meta};
@@ -594,48 +594,43 @@ impl ReadPlan {
594
594
  }
595
595
  }
596
596
 
597
- fn build(header: &Header, scope: &[&str], window: u64) -> Result<ReadPlan> {
597
+ fn build(ordered: &[(&str, &str, &Tensor)], window: u64) -> Result<ReadPlan> {
598
598
  let mut items = Vec::new();
599
599
  let mut dest: u64 = 0;
600
- for (comp, tensors) in &header.components {
601
- if !scope.contains(&comp.as_str()) {
602
- continue;
603
- }
604
- for (key, tensor) in tensors {
605
- for (role, part) in &tensor.parts {
606
- let what = format!("{comp}/{key}#{role}");
607
- if let Body::Inline(b) = &part.body {
608
- let n = part.nbytes(&what)?;
600
+ for (comp, key, tensor) in ordered {
601
+ for (role, part) in &tensor.parts {
602
+ let what = format!("{comp}/{key}#{role}");
603
+ if let Body::Inline(b) = &part.body {
604
+ let n = part.nbytes(&what)?;
605
+ items.push(PlanItem {
606
+ what: what.clone(),
607
+ dest_off: dest,
608
+ len: n,
609
+ source: Source::Inline(b.clone()),
610
+ });
611
+ dest += n;
612
+ continue;
613
+ }
614
+ for segment in part.segments() {
615
+ let mut off = 0u64;
616
+ while off < segment.length {
617
+ let n = if window == 0 {
618
+ segment.length - off
619
+ } else {
620
+ window.min(segment.length - off)
621
+ };
609
622
  items.push(PlanItem {
610
623
  what: what.clone(),
611
624
  dest_off: dest,
612
625
  len: n,
613
- source: Source::Inline(b.clone()),
626
+ source: Source::Object(ObjectRange {
627
+ obj: segment.clone(),
628
+ off,
629
+ len: n,
630
+ }),
614
631
  });
615
632
  dest += n;
616
- continue;
617
- }
618
- for segment in part.segments() {
619
- let mut off = 0u64;
620
- while off < segment.length {
621
- let n = if window == 0 {
622
- segment.length - off
623
- } else {
624
- window.min(segment.length - off)
625
- };
626
- items.push(PlanItem {
627
- what: what.clone(),
628
- dest_off: dest,
629
- len: n,
630
- source: Source::Object(ObjectRange {
631
- obj: segment.clone(),
632
- off,
633
- len: n,
634
- }),
635
- });
636
- dest += n;
637
- off += n;
638
- }
633
+ off += n;
639
634
  }
640
635
  }
641
636
  }
@@ -658,8 +653,10 @@ fn tally(items: &[PlanItem]) -> (u64, u64, usize) {
658
653
  (bytes, io_bytes, inline)
659
654
  }
660
655
 
661
- /// The read planner. Runtime's traversal must exactly equal the stored construction order;
662
- /// after that equality gate, planning walks the header once and never performs key lookup.
656
+ /// The read planner. The destination follows the CALLER's traversal, which is the order it
657
+ /// constructs in; the header's tensor order is a storage choice of whoever wrote it. The two
658
+ /// are written by independently versioned peers, so a permutation is planned, never refused:
659
+ /// only a traversal that names a different set of tensors is.
663
660
  ///
664
661
  /// COMPLETENESS SCOPES TO THE REQUESTED COMPONENT SET (#570b). `requested` is the caller's
665
662
  /// explicit declaration of which components this plan is for; the traversal must name every
@@ -716,34 +713,38 @@ pub fn plan_for_traversal(
716
713
  }
717
714
  }
718
715
 
719
- let named: Vec<(&str, &str)> = traversal
720
- .iter()
721
- .map(|(c, k)| (c.as_str(), k.as_str()))
722
- .collect();
723
- let have: Vec<(&str, &str)> = header
716
+ let mut carried: HashMap<(&str, &str), &Tensor> = header
724
717
  .tensors()
725
718
  .filter(|(c, _, _)| scope.contains(&c.as_str()))
726
- .map(|(c, k, _)| (c.as_str(), k.as_str()))
719
+ .map(|(c, k, t)| ((c.as_str(), k.as_str()), t))
727
720
  .collect();
728
- if named != have {
729
- let at = named
730
- .iter()
731
- .zip(have.iter())
732
- .position(|(got, want)| got != want)
733
- .unwrap_or(named.len().min(have.len()));
734
- let got = named.get(at).map(|(c, k)| format!("{c}/{k}"));
735
- let want = have.get(at).map(|(c, k)| format!("{c}/{k}"));
721
+ let mut ordered = Vec::with_capacity(traversal.len());
722
+ for (c, k) in traversal {
723
+ let Some(tensor) = carried.remove(&(c.as_str(), k.as_str())) else {
724
+ return refuse(
725
+ Code::TRAVERSAL_INCOMPLETE,
726
+ format!(
727
+ "the traversal names {c}/{k}, which requested components {scope:?} do \
728
+ not carry, or names it twice"
729
+ ),
730
+ );
731
+ };
732
+ ordered.push((c.as_str(), k.as_str(), tensor));
733
+ }
734
+ if !carried.is_empty() {
735
+ let mut missing: Vec<String> = carried.keys().map(|(c, k)| format!("{c}/{k}")).collect();
736
+ missing.sort();
737
+ missing.truncate(4);
736
738
  return refuse(
737
- Code::TRAVERSAL_ORDER_MISMATCH,
739
+ Code::TRAVERSAL_INCOMPLETE,
738
740
  format!(
739
- "construction order differs at row {at}: caller {got:?}, header {want:?}; \
740
- caller has {} rows and requested components {scope:?} carry {}",
741
- named.len(),
742
- have.len()
741
+ "the traversal omits {} tensor(s) requested components {scope:?} carry: \
742
+ {missing:?}",
743
+ carried.len()
743
744
  ),
744
745
  );
745
746
  }
746
- build(header, &scope, window)
747
+ build(&ordered, window)
747
748
  }
748
749
 
749
750
  // ---------------------------------------------------------------- the reader pool
@@ -158,6 +158,14 @@ const EDQUOT: i32 = 69;
158
158
  /// the owner re-issued the fetch, which re-planned, re-asked, and failed at the same byte.
159
159
  /// On rented hardware, forever. Naming the condition is what lets a caller stop.
160
160
  pub(crate) fn classify_io(what: impl AsRef<str>, e: std::io::Error) -> Refusal {
161
+ // A full descriptor table is the process's shortage, not the Store's: every open in
162
+ // the Store says so the same way, and a transfer treats it as backpressure.
163
+ if crate::descriptors::exhausted(&e) {
164
+ return Refusal {
165
+ code: Code::FD_HEADROOM,
166
+ detail: format!("{}: no file descriptor ({e})", what.as_ref()),
167
+ };
168
+ }
161
169
  if matches!(e.raw_os_error(), Some(ENOSPC) | Some(EDQUOT)) {
162
170
  return Refusal {
163
171
  code: Code::CAPACITY_EXHAUSTED,
@@ -1362,16 +1370,7 @@ impl Store {
1362
1370
  .create_new(true)
1363
1371
  .mode(0o600)
1364
1372
  .open(&tmp)
1365
- .map_err(|e| {
1366
- if crate::descriptors::exhausted(&e) {
1367
- Refusal {
1368
- code: Code::FD_HEADROOM,
1369
- detail: format!("create temp {}: {e}", tmp.display()),
1370
- }
1371
- } else {
1372
- io(format!("create temp {}", tmp.display()), e)
1373
- }
1374
- })?;
1373
+ .map_err(|e| io(format!("create temp {}", tmp.display()), e))?;
1375
1374
  match f.try_lock_exclusive() {
1376
1375
  Ok(()) => break (tmp, f),
1377
1376
  // Only a reaper can hold the lock on a name this process just minted: it
@@ -1473,7 +1472,10 @@ impl Store {
1473
1472
  fault: &Fault,
1474
1473
  operation: Option<&str>,
1475
1474
  ) -> Result<Put> {
1476
- let catalog = crate::catalog::Catalog::open(&self.root)?;
1475
+ let catalog = match crate::catalog::Catalog::open_admitting(&self.root) {
1476
+ Ok(catalog) => catalog,
1477
+ Err(refusal) => return cleanup(&verified.tmp, refusal),
1478
+ };
1477
1479
  let _writer = crate::catalog::WriterGuard::acquire(&self.root)?;
1478
1480
  let object = ObjectRef {
1479
1481
  sha256: verified.sha256.clone(),