tensorfs 0.3.56__tar.gz → 0.3.57__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 (148) hide show
  1. {tensorfs-0.3.56 → tensorfs-0.3.57}/Cargo.lock +2 -2
  2. {tensorfs-0.3.56 → tensorfs-0.3.57}/Cargo.toml +1 -1
  3. {tensorfs-0.3.56 → tensorfs-0.3.57}/PKG-INFO +1 -1
  4. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/Cargo.toml +1 -1
  5. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -3
  6. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/cache_roots.rs +1 -0
  7. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/canon.rs +61 -1
  8. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/catalog.rs +45 -41
  9. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/resume_tests.rs +1 -0
  10. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived.rs +1 -0
  11. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/fetch.rs +20 -32
  12. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/fingerprint.rs +19 -25
  13. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/preflight.rs +14 -2
  14. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/source.rs +101 -31
  15. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/transaction.rs +1 -0
  16. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/manifest.rs +57 -28
  17. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/read.rs +29 -14
  18. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/repository.rs +91 -32
  19. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/storage.rs +27 -3
  20. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/store.rs +4 -2
  21. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/http.rs +74 -3
  22. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/hub.rs +5 -2
  23. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/pull.rs +1 -0
  24. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/push.rs +24 -29
  25. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/tests.rs +5 -0
  26. tensorfs-0.3.57/crates/tensorfs-core/tests/additive_fields.rs +167 -0
  27. tensorfs-0.3.57/crates/tensorfs-core/tests/fd_headroom.rs +75 -0
  28. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/repo_store.rs +2 -0
  29. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/source_preflight.rs +21 -8
  30. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/store_boundaries.rs +4 -1
  31. tensorfs-0.3.57/crates/tensorfs-core/tests/transfer_memory.rs +179 -0
  32. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/src/store.rs +2 -0
  33. {tensorfs-0.3.56 → tensorfs-0.3.57}/pyproject.toml +1 -1
  34. {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/_derived_channel.py +6 -8
  35. {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/_source_channel.py +2 -2
  36. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/build.rs +0 -0
  37. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  38. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  39. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  40. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  41. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/asset_update.rs +0 -0
  42. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/b64.rs +0 -0
  43. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  44. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  45. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  46. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
  47. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
  48. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  49. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  50. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  51. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/capability.rs +0 -0
  52. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/cbor.rs +0 -0
  53. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  54. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
  55. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/corpus.rs +0 -0
  56. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  57. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/companion_tests.rs +0 -0
  58. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  59. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  60. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  61. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/retention.rs +0 -0
  62. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/roots.rs +0 -0
  63. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/dtype.rs +0 -0
  64. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/durability.rs +0 -0
  65. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/err.rs +0 -0
  66. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/fill.rs +0 -0
  67. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/fit.rs +0 -0
  68. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/gc.rs +0 -0
  69. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/header.rs +0 -0
  70. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/home.rs +0 -0
  71. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ids.rs +0 -0
  72. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  73. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
  74. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  75. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
  76. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  77. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  78. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  79. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/jcs.rs +0 -0
  80. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/lib.rs +0 -0
  81. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/limits.rs +0 -0
  82. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/machine.rs +0 -0
  83. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/meta.rs +0 -0
  84. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/project.rs +0 -0
  85. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/providers.rs +0 -0
  86. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/receipt.rs +0 -0
  87. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/reclaim.rs +0 -0
  88. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/registry.rs +0 -0
  89. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/relation.rs +0 -0
  90. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/repo_cache/identity.rs +0 -0
  91. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/repo_cache.rs +0 -0
  92. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/sha256.rs +0 -0
  93. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/source_artifact.rs +0 -0
  94. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/spec.rs +0 -0
  95. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/staging.rs +0 -0
  96. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/stats.rs +0 -0
  97. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/api.rs +0 -0
  98. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +0 -0
  99. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  100. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  101. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  102. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
  103. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
  104. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/speculate.rs +0 -0
  105. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/vectors.rs +0 -0
  106. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
  107. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  108. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fit.rs +0 -0
  109. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  110. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  111. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  112. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  113. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  114. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  115. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  116. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
  117. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
  118. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  119. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/home.rs +0 -0
  120. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  121. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  122. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/liveness.rs +0 -0
  123. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/qwen_image21_profile.rs +0 -0
  124. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/read_plan.rs +0 -0
  125. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  126. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  127. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  128. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  129. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  130. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  131. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  132. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  133. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
  134. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/staging.rs +0 -0
  135. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  136. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/trust_index.rs +0 -0
  137. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/version.rs +0 -0
  138. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/write_through.rs +0 -0
  139. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/Cargo.toml +0 -0
  140. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/src/docs.rs +0 -0
  141. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/src/errors.rs +0 -0
  142. {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/src/lib.rs +0 -0
  143. {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/__init__.py +0 -0
  144. {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/_ext.pyi +0 -0
  145. {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/_tensors.py +0 -0
  146. {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/derived.py +0 -0
  147. {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/errors.py +0 -0
  148. {tensorfs-0.3.56 → tensorfs-0.3.57}/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.56"
442
+ version = "0.3.57"
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.56"
457
+ version = "0.3.57"
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.56"
6
+ version = "0.3.57"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.56
3
+ Version: 0.3.57
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 @@ path = "src/bin/tfs/main.rs"
11
11
  fs2 = "0.4"
12
12
  minicbor = { version = "2.3.0", default-features = false, features = ["alloc"] }
13
13
  rusqlite = { version = "0.32", features = ["bundled"] }
14
- rustix = { version = "1.1.4", default-features = false, features = ["fs", "std", "net", "event"] }
14
+ rustix = { version = "1.1.4", default-features = false, features = ["fs", "std", "net", "event", "process"] }
15
15
  # tfs-049: the ONE transport (src/transport/). rustls is the one TLS stack, used only
16
16
  # there; scripts/fence.py refuses it anywhere else so a second mover cannot grow.
17
17
  rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
@@ -80,9 +80,6 @@ pub fn cmd_manifest_build(entries_path: &Path, flags: &Flags) -> ExitCode {
80
80
  Ok(path) => path.to_string(),
81
81
  Err(error) => return bail(error),
82
82
  };
83
- if let Err(error) = fields.done() {
84
- return bail(error);
85
- }
86
83
  files.push((path, blob));
87
84
  }
88
85
  let manifest = match Manifest::from_files(files) {
@@ -316,6 +316,7 @@ mod tests {
316
316
  expected_revision: 0,
317
317
  remove: vec![],
318
318
  set: vec![crate::repository::ReleaseLane {
319
+ extra: Default::default(),
319
320
  lane: "bf16".into(),
320
321
  manifest,
321
322
  }],
@@ -36,6 +36,14 @@ impl Value {
36
36
  v.sort_by(|a, b| a.0.cmp(&b.0));
37
37
  Value::Obj(v)
38
38
  }
39
+ /// `obj` plus the fields a parse carried through unread.
40
+ pub fn obj_with(pairs: Vec<(&str, Value)>, extra: &Extra) -> Value {
41
+ let mut v: Vec<(String, Value)> =
42
+ pairs.into_iter().map(|(k, x)| (k.to_string(), x)).collect();
43
+ v.extend(extra.0.iter().cloned());
44
+ v.sort_by(|a, b| a.0.cmp(&b.0));
45
+ Value::Obj(v)
46
+ }
39
47
  pub fn str(s: impl Into<String>) -> Value {
40
48
  Value::Str(s.into())
41
49
  }
@@ -357,7 +365,47 @@ impl<'a> P<'a> {
357
365
  }
358
366
  }
359
367
 
360
- // ---------------------------------------------------------------- strict field reader
368
+ // ---------------------------------------------------------------- field reader
369
+
370
+ /// Fields this build does not read. A stored document carries them through unchanged, so a
371
+ /// newer writer's additions keep their bytes, identity and meaning across an older reader.
372
+ #[derive(Debug, Clone, PartialEq, Eq, Default)]
373
+ pub struct Extra(Vec<(String, Value)>);
374
+
375
+ impl Extra {
376
+ /// Every `{sha256, length}` object anywhere in the unread fields. Reachability treats
377
+ /// them as referenced: GC never deletes what a newer writer may have named.
378
+ pub fn refs(&self) -> Vec<crate::ids::ObjectRef> {
379
+ fn walk(value: &Value, out: &mut Vec<crate::ids::ObjectRef>) {
380
+ match value {
381
+ Value::Arr(items) => items.iter().for_each(|item| walk(item, out)),
382
+ Value::Obj(pairs) => {
383
+ let sha256 = pairs.iter().find_map(|(k, v)| match (k.as_str(), v) {
384
+ ("sha256", Value::Str(s)) => Some(s),
385
+ _ => None,
386
+ });
387
+ let length = pairs.iter().find_map(|(k, v)| match (k.as_str(), v) {
388
+ ("length", Value::Int(n)) if *n >= 0 => Some(*n as u64),
389
+ _ => None,
390
+ });
391
+ if let (Some(sha256), Some(length)) = (sha256, length) {
392
+ if crate::ids::hex64("unread reference", sha256).is_ok() {
393
+ out.push(crate::ids::ObjectRef {
394
+ sha256: sha256.clone(),
395
+ length,
396
+ });
397
+ }
398
+ }
399
+ pairs.iter().for_each(|(_, v)| walk(v, out));
400
+ }
401
+ _ => {}
402
+ }
403
+ }
404
+ let mut out = Vec::new();
405
+ self.0.iter().for_each(|(_, v)| walk(v, &mut out));
406
+ out
407
+ }
408
+ }
361
409
 
362
410
  pub struct Fields<'a> {
363
411
  what: &'static str,
@@ -396,6 +444,8 @@ impl<'a> Fields<'a> {
396
444
  pub fn req_uint(&mut self, key: &str) -> Result<u64> {
397
445
  as_uint(self.what, key, self.req(key)?)
398
446
  }
447
+ /// End a document this build must understand completely: a command, or a record whose
448
+ /// unread field could change what it means.
399
449
  pub fn done(self) -> Result<()> {
400
450
  match self.left.first() {
401
451
  None => Ok(()),
@@ -405,6 +455,16 @@ impl<'a> Fields<'a> {
405
455
  ),
406
456
  }
407
457
  }
458
+
459
+ /// End a document that tolerates additive fields, keeping them for re-emission.
460
+ pub fn rest(self) -> Extra {
461
+ Extra(
462
+ self.left
463
+ .into_iter()
464
+ .map(|(k, v)| (k.to_string(), v.clone()))
465
+ .collect(),
466
+ )
467
+ }
408
468
  }
409
469
 
410
470
  pub fn as_str<'a>(what: &str, key: &str, v: &'a Value) -> Result<&'a str> {
@@ -19,6 +19,9 @@ use crate::storage::Projection;
19
19
  /// the floor under any patience a supervisor grants a worker that has gone quiet.
20
20
  pub const BUSY_TIMEOUT: Duration = Duration::from_secs(5);
21
21
 
22
+ /// The catalog this build writes. A newer build may only ADD tables and columns (an
23
+ /// incompatible change renames the table), so any catalog at this version or later that
24
+ /// holds every table and column below opens.
22
25
  const SCHEMA_VERSION: u64 = 2;
23
26
 
24
27
  const SCHEMA: &[(&str, &[&str])] = &[
@@ -63,50 +66,25 @@ fn io(what: impl AsRef<str>, error: std::io::Error) -> Refusal {
63
66
  crate::store::classify_io(what, error)
64
67
  }
65
68
 
69
+ /// Every table and column this build uses is present. Additions by a newer build are its own.
66
70
  fn verify_schema(connection: &Connection) -> Result<()> {
67
- let mut statement = connection
68
- .prepare(
69
- "SELECT name FROM sqlite_master
70
- WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
71
- )
72
- .map_err(|error| sql("prepare catalog schema census", error))?;
73
- let tables: Vec<String> = statement
74
- .query_map([], |row| row.get(0))
75
- .map_err(|error| sql("read catalog schema census", error))?
76
- .collect::<std::result::Result<_, _>>()
77
- .map_err(|error| sql("collect catalog schema census", error))?;
78
- let expected: Vec<String> = SCHEMA.iter().map(|(name, _)| (*name).to_string()).collect();
79
- if tables != expected {
80
- return refuse(
81
- Code::UNKNOWN_FORMAT,
82
- "tensorfs.sqlite is not the exact v1 catalog; remove it and rebuild from repository files",
83
- );
84
- }
85
71
  for (table, expected_columns) in SCHEMA {
86
72
  let mut columns = connection
87
- .prepare("SELECT name FROM pragma_table_info(?1) ORDER BY cid")
73
+ .prepare("SELECT name FROM pragma_table_info(?1)")
88
74
  .map_err(|error| sql("prepare catalog column census", error))?;
89
75
  let actual: Vec<String> = columns
90
76
  .query_map([table], |row| row.get(0))
91
77
  .map_err(|error| sql("read catalog column census", error))?
92
78
  .collect::<std::result::Result<_, _>>()
93
79
  .map_err(|error| sql("collect catalog column census", error))?;
94
- let expected: Vec<String> = expected_columns
80
+ if let Some(missing) = expected_columns
95
81
  .iter()
96
- .map(|name| (*name).to_string())
97
- .collect();
98
- let strict: u64 = connection
99
- .query_row(
100
- "SELECT strict FROM pragma_table_list WHERE schema='main' AND name=?1",
101
- [table],
102
- |row| row.get(0),
103
- )
104
- .map_err(|error| sql("read catalog strict-table census", error))?;
105
- if actual != expected || strict != 1 {
82
+ .find(|column| !actual.iter().any(|name| name == *column))
83
+ {
106
84
  return refuse(
107
85
  Code::UNKNOWN_FORMAT,
108
86
  format!(
109
- "tensorfs.sqlite table {table} is not the exact v1 shape; remove the catalog and rebuild from repository files"
87
+ "tensorfs.sqlite table {table} has no column {missing}; remove the catalog and rebuild from repository files"
110
88
  ),
111
89
  );
112
90
  }
@@ -162,10 +140,12 @@ fn validated(path: &Path) -> Result<Connection> {
162
140
  let version: u64 = connection
163
141
  .query_row("PRAGMA user_version", [], |row| row.get(0))
164
142
  .map_err(|error| sql("read catalog version", error))?;
165
- if version != SCHEMA_VERSION {
143
+ if version < SCHEMA_VERSION {
166
144
  return refuse(
167
145
  Code::UNKNOWN_FORMAT,
168
- format!("tensorfs.sqlite user_version {version} != {SCHEMA_VERSION}"),
146
+ format!(
147
+ "tensorfs.sqlite user_version {version} predates {SCHEMA_VERSION}; remove it and rebuild from repository files"
148
+ ),
169
149
  );
170
150
  }
171
151
  verify_schema(&connection)?;
@@ -410,7 +390,7 @@ impl Catalog {
410
390
  let version: u64 = connection
411
391
  .query_row("PRAGMA user_version", [], |row| row.get(0))
412
392
  .map_err(|error| sql("read catalog version", error))?;
413
- if version == SCHEMA_VERSION {
393
+ if version >= SCHEMA_VERSION {
414
394
  connection
415
395
  .execute_batch("COMMIT")
416
396
  .map_err(|error| sql("commit catalog open", error))?;
@@ -421,7 +401,7 @@ impl Catalog {
421
401
  return refuse(
422
402
  Code::UNKNOWN_FORMAT,
423
403
  format!(
424
- "tensorfs.sqlite user_version {version} is not supported; remove it and rebuild from repository files"
404
+ "tensorfs.sqlite user_version {version} predates {SCHEMA_VERSION}; remove it and rebuild from repository files"
425
405
  ),
426
406
  );
427
407
  }
@@ -1418,7 +1398,7 @@ mod tests {
1418
1398
  }
1419
1399
 
1420
1400
  #[test]
1421
- fn catalog_has_seven_tables_and_refuses_future_versions_without_mutation() {
1401
+ fn catalog_has_seven_tables_and_opens_additive_future_versions() {
1422
1402
  let root = temporary("schema");
1423
1403
  fs::create_dir_all(&root).unwrap();
1424
1404
  let catalog = Catalog::initialize(&root).unwrap();
@@ -1448,17 +1428,41 @@ mod tests {
1448
1428
  "tensorfs_verified_blobs",
1449
1429
  ]
1450
1430
  );
1451
- connection.execute_batch("PRAGMA user_version=3").unwrap();
1431
+ // A newer build added a table and a column: this build still opens and uses it.
1432
+ connection
1433
+ .execute_batch(
1434
+ "CREATE TABLE tensorfs_future(value TEXT);
1435
+ ALTER TABLE tensorfs_holds ADD COLUMN note TEXT;
1436
+ PRAGMA user_version=3;",
1437
+ )
1438
+ .unwrap();
1452
1439
  drop(connection);
1453
- assert_eq!(
1454
- Catalog::initialize(&root).unwrap_err().code,
1455
- Code::UNKNOWN_FORMAT
1456
- );
1440
+ let reopened = Catalog::initialize(&root).unwrap();
1441
+ let guard = reopened.begin_operation("op", "org", "repo").unwrap();
1442
+ reopened
1443
+ .hold(
1444
+ "op",
1445
+ &crate::storage::blob_key(&"ab".repeat(32)).unwrap(),
1446
+ 7,
1447
+ )
1448
+ .unwrap();
1449
+ drop(guard);
1450
+ drop(reopened);
1451
+ Catalog::open(&root).unwrap();
1457
1452
  let connection = rusqlite::Connection::open(Catalog::path(&root)).unwrap();
1458
1453
  let version: u64 = connection
1459
1454
  .query_row("PRAGMA user_version", [], |row| row.get(0))
1460
1455
  .unwrap();
1461
1456
  assert_eq!(version, 3);
1457
+ // A catalog without a column this build uses is genuinely unreadable.
1458
+ connection
1459
+ .execute_batch("ALTER TABLE tensorfs_verified_blobs DROP COLUMN mtime_ns")
1460
+ .unwrap();
1461
+ drop(connection);
1462
+ assert_eq!(
1463
+ Catalog::initialize(&root).unwrap_err().code,
1464
+ Code::UNKNOWN_FORMAT
1465
+ );
1462
1466
  let _ = fs::remove_dir_all(root);
1463
1467
  }
1464
1468
 
@@ -437,6 +437,7 @@ fn source_identity_is_locked_and_source_closure_stays_rooted_after_writer_death(
437
437
  repo: repo.clone(),
438
438
  remove: Vec::new(),
439
439
  set: vec![ReleaseLane {
440
+ extra: Default::default(),
440
441
  lane: "main".into(),
441
442
  manifest: receipt.manifest.clone(),
442
443
  }],
@@ -4191,6 +4191,7 @@ mod tests {
4191
4191
  repo,
4192
4192
  remove: Vec::new(),
4193
4193
  set: vec![ReleaseLane {
4194
+ extra: Default::default(),
4194
4195
  lane: "main".into(),
4195
4196
  manifest: manifest_ref.clone(),
4196
4197
  }],
@@ -35,26 +35,35 @@ pub fn parse_refs_jsonl(bytes: &[u8]) -> Result<Vec<ObjectRef>> {
35
35
  .split(|byte| *byte == b'\n')
36
36
  .filter(|line| !line.is_empty())
37
37
  {
38
- let value = canon::parse_canonical(line, 16 * 1024)?;
38
+ let value = canon::parse(line, 16 * 1024)?;
39
39
  let mut fields = Fields::new("FetchRef", &value)?;
40
40
  let length = fields.req_uint("length")?;
41
41
  let sha256 = hex64("FetchRef.sha256", fields.req_str("sha256")?)?;
42
- fields.done()?;
43
42
  result.push(ObjectRef { sha256, length });
44
43
  }
45
44
  if result.is_empty() {
46
45
  return refuse(Code::MISSING_FIELD, "fetch refs are empty");
47
46
  }
48
- if result
47
+ sorted_refs("fetch refs", result)
48
+ }
49
+
50
+ /// Digest order with identical repeats merged. One digest at two lengths still refuses.
51
+ fn sorted_refs(what: &str, mut refs: Vec<ObjectRef>) -> Result<Vec<ObjectRef>> {
52
+ refs.sort_by(|a, b| a.sha256.cmp(&b.sha256).then(a.length.cmp(&b.length)));
53
+ refs.dedup();
54
+ if let Some(pair) = refs
49
55
  .windows(2)
50
- .any(|pair| pair[0].sha256 >= pair[1].sha256)
56
+ .find(|pair| pair[0].sha256 == pair[1].sha256)
51
57
  {
52
58
  return refuse(
53
- Code::SORT_ORDER,
54
- "fetch refs must be strictly sorted and unique by digest",
59
+ Code::LENGTH_MISMATCH,
60
+ format!(
61
+ "{what} name sha256:{} at {} B and at {} B",
62
+ pair[0].sha256, pair[0].length, pair[1].length
63
+ ),
55
64
  );
56
65
  }
57
- Ok(result)
66
+ Ok(refs)
58
67
  }
59
68
 
60
69
  pub fn final_key(sha256: &str) -> Result<String> {
@@ -148,16 +157,7 @@ impl FetchPlan {
148
157
  something a worker can pull",
149
158
  );
150
159
  }
151
- let mut sorted: Vec<ObjectRef> = declared.to_vec();
152
- sorted.sort_by(|a, b| a.sha256.cmp(&b.sha256));
153
- sorted.dedup_by(|a, b| a.sha256 == b.sha256);
154
- if sorted.len() != declared.len() {
155
- return refuse(
156
- Code::SORT_ORDER,
157
- "the declared object set lists one digest twice — two opinions about how many \
158
- bytes this pull moves",
159
- );
160
- }
160
+ let mut sorted = sorted_refs("the declared object set", declared.to_vec())?;
161
161
  if let Some(manifest) = manifest {
162
162
  if !sorted.iter().any(|object| object.sha256 == manifest.sha256) {
163
163
  sorted.push(manifest.clone());
@@ -318,7 +318,7 @@ fn presence(store: &Store, o: &ObjectRef) -> Result<Presence> {
318
318
 
319
319
  impl FetchPlan {
320
320
  pub fn parse(bytes: &[u8]) -> Result<Self> {
321
- Self::from_value(&canon::parse_canonical(bytes, limits::DOC_MAX_BYTES)?)
321
+ Self::from_value(&canon::parse(bytes, limits::DOC_MAX_BYTES)?)
322
322
  }
323
323
  pub fn canonical_bytes(&self) -> Vec<u8> {
324
324
  canon::write(&self.to_value())
@@ -343,20 +343,9 @@ impl FetchPlan {
343
343
  for o in as_arr("FetchPlan", "wanted", f.req("wanted")?)? {
344
344
  wanted.push(ObjectRef::from_value("FetchPlan.wanted", o)?);
345
345
  }
346
- f.done()?;
347
346
  ascii_name("FetchPlan.session", &session, limits::MAX_NAME_BYTES)?;
348
- for (what, set) in [("held", &held), ("wanted", &wanted)] {
349
- let mut ids: Vec<&str> = set.iter().map(|o| o.sha256.as_str()).collect();
350
- let given = ids.clone();
351
- ids.sort_unstable();
352
- ids.dedup();
353
- if ids != given {
354
- return refuse(
355
- Code::SORT_ORDER,
356
- format!("FetchPlan.{what} must be sorted and unique by digest"),
357
- );
358
- }
359
- }
347
+ let held = sorted_refs("FetchPlan.held", held)?;
348
+ let wanted = sorted_refs("FetchPlan.wanted", wanted)?;
360
349
  if held.is_empty() && wanted.is_empty() {
361
350
  return refuse(Code::MISSING_FIELD, "a fetch plan covers no object");
362
351
  }
@@ -921,7 +910,6 @@ impl DeliveryGrant {
921
910
  };
922
911
  let object = ObjectRef::from_value("DeliveryGrant.object", f.req("object")?)?;
923
912
  let session = f.req_str("session")?.to_string();
924
- f.done()?;
925
913
  ascii_name("DeliveryGrant.session", &session, limits::MAX_NAME_BYTES)?;
926
914
  Ok(DeliveryGrant {
927
915
  session,
@@ -508,34 +508,28 @@ impl FingerprintRegistry {
508
508
  provenance,
509
509
  });
510
510
  }
511
+ // A newer registry's additional top-level sections belong to a newer reader.
511
512
  let source_profiles = match f.opt("source_profiles") {
512
513
  None => Vec::new(),
513
514
  Some(value) => parse_source_profiles(value)?,
514
515
  };
515
- f.done()?;
516
- let mut seen: Vec<(&str, &str, &str)> = entries
517
- .iter()
518
- .map(|e| {
519
- (
520
- e.keyset_digest.as_str(),
521
- e.component.as_str(),
522
- e.tensor_schema_digest.as_str(),
523
- )
524
- })
525
- .collect();
526
- let given = seen.clone();
527
- seen.sort();
528
- if seen != given {
529
- return refuse(
530
- Code::SORT_ORDER,
531
- "registry entries must be sorted by (keyset_digest, component, tensor_schema_digest)",
532
- );
533
- }
534
- seen.dedup();
535
- if seen.len() != entries.len() {
516
+ let key = |e: &Banked| {
517
+ (
518
+ e.keyset_digest.clone(),
519
+ e.component.clone(),
520
+ e.tensor_schema_digest.clone(),
521
+ )
522
+ };
523
+ entries.sort_by_key(key);
524
+ entries.dedup();
525
+ if entries
526
+ .windows(2)
527
+ .any(|pair| key(&pair[0]) == key(&pair[1]))
528
+ {
536
529
  return refuse(
537
530
  Code::DUPLICATE_KEY,
538
- "two entries bank the same (keyset_digest, component, tensor_schema_digest)",
531
+ "two entries bank the same (keyset_digest, component, tensor_schema_digest) \
532
+ with different verdicts",
539
533
  );
540
534
  }
541
535
  for profile in &source_profiles {
@@ -997,12 +991,12 @@ mod tests {
997
991
  assert!(!String::from_utf8_lossy(&bytes).contains("format"));
998
992
  assert_eq!(FingerprintRegistry::parse(&bytes).unwrap(), registry);
999
993
 
1000
- let old = String::from_utf8(bytes)
994
+ let newer = String::from_utf8(bytes)
1001
995
  .unwrap()
1002
996
  .replacen('{', r#"{"format":"retired","#, 1);
1003
997
  assert_eq!(
1004
- FingerprintRegistry::parse(old.as_bytes()).unwrap_err().code,
1005
- Code::UNKNOWN_FIELD
998
+ FingerprintRegistry::parse(newer.as_bytes()).unwrap(),
999
+ registry
1006
1000
  );
1007
1001
  }
1008
1002
 
@@ -262,9 +262,21 @@ pub fn plan(
262
262
  }
263
263
  let mut sorted: Vec<&str> = profiles.iter().map(String::as_str).collect();
264
264
  sorted.sort();
265
- if sorted.windows(2).any(|window| window[0] == window[1]) {
266
- return refuse(Code::DUPLICATE_KEY, "source profiles must be unique");
265
+ sorted.dedup();
266
+ let mut unique: Vec<MemberHead> = Vec::with_capacity(heads.len());
267
+ for head in heads {
268
+ match unique.iter().find(|kept| kept.member == head.member) {
269
+ Some(kept) if kept == head => {}
270
+ Some(_) => {
271
+ return refuse(
272
+ Code::DUPLICATE_KEY,
273
+ format!("{} is listed twice with different heads", head.member),
274
+ )
275
+ }
276
+ None => unique.push(head.clone()),
277
+ }
267
278
  }
279
+ let heads = unique.as_slice();
268
280
  let staged = stage(scratch, heads)?;
269
281
  let mut plans = Vec::with_capacity(profiles.len());
270
282
  for profile in &sorted {