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.
- {tensorfs-0.3.56 → tensorfs-0.3.57}/Cargo.lock +2 -2
- {tensorfs-0.3.56 → tensorfs-0.3.57}/Cargo.toml +1 -1
- {tensorfs-0.3.56 → tensorfs-0.3.57}/PKG-INFO +1 -1
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/Cargo.toml +1 -1
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -3
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/cache_roots.rs +1 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/canon.rs +61 -1
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/catalog.rs +45 -41
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/resume_tests.rs +1 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived.rs +1 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/fetch.rs +20 -32
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/fingerprint.rs +19 -25
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/preflight.rs +14 -2
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/source.rs +101 -31
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/transaction.rs +1 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/manifest.rs +57 -28
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/read.rs +29 -14
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/repository.rs +91 -32
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/storage.rs +27 -3
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/store.rs +4 -2
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/http.rs +74 -3
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/hub.rs +5 -2
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/pull.rs +1 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/push.rs +24 -29
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/tests.rs +5 -0
- tensorfs-0.3.57/crates/tensorfs-core/tests/additive_fields.rs +167 -0
- tensorfs-0.3.57/crates/tensorfs-core/tests/fd_headroom.rs +75 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/repo_store.rs +2 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/source_preflight.rs +21 -8
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/store_boundaries.rs +4 -1
- tensorfs-0.3.57/crates/tensorfs-core/tests/transfer_memory.rs +179 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/src/store.rs +2 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/pyproject.toml +1 -1
- {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/_derived_channel.py +6 -8
- {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/_source_channel.py +2 -2
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/asset_update.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/companion_tests.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/meta.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/repo_cache/identity.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/stats.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/http_reuse_tests.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/transport/speculate.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/foreign_json.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/qwen_image21_profile.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/trust_index.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/_ext.pyi +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/_tensors.py +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/derived.py +0 -0
- {tensorfs-0.3.56 → tensorfs-0.3.57}/python/tensorfs/errors.py +0 -0
- {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.
|
|
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.
|
|
457
|
+
version = "0.3.57"
|
|
458
458
|
dependencies = [
|
|
459
459
|
"pyo3",
|
|
460
460
|
"tensorfs-core",
|
|
@@ -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) {
|
|
@@ -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
|
-
// ----------------------------------------------------------------
|
|
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)
|
|
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
|
|
80
|
+
if let Some(missing) = expected_columns
|
|
95
81
|
.iter()
|
|
96
|
-
.
|
|
97
|
-
|
|
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}
|
|
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
|
|
143
|
+
if version < SCHEMA_VERSION {
|
|
166
144
|
return refuse(
|
|
167
145
|
Code::UNKNOWN_FORMAT,
|
|
168
|
-
format!(
|
|
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
|
|
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}
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
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
|
}],
|
|
@@ -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::
|
|
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
|
-
|
|
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
|
-
.
|
|
56
|
+
.find(|pair| pair[0].sha256 == pair[1].sha256)
|
|
51
57
|
{
|
|
52
58
|
return refuse(
|
|
53
|
-
Code::
|
|
54
|
-
|
|
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(
|
|
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
|
|
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::
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
(
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
.
|
|
526
|
-
|
|
527
|
-
|
|
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
|
|
994
|
+
let newer = String::from_utf8(bytes)
|
|
1001
995
|
.unwrap()
|
|
1002
996
|
.replacen('{', r#"{"format":"retired","#, 1);
|
|
1003
997
|
assert_eq!(
|
|
1004
|
-
FingerprintRegistry::parse(
|
|
1005
|
-
|
|
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
|
-
|
|
266
|
-
|
|
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 {
|