tensorfs 0.3.41__tar.gz → 0.3.43__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 (136) hide show
  1. {tensorfs-0.3.41 → tensorfs-0.3.43}/Cargo.lock +3 -2
  2. {tensorfs-0.3.41 → tensorfs-0.3.43}/Cargo.toml +1 -1
  3. {tensorfs-0.3.41 → tensorfs-0.3.43}/PKG-INFO +1 -1
  4. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/Cargo.toml +1 -0
  5. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/ingest.rs +29 -1
  6. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/cbor.rs +20 -0
  7. tensorfs-0.3.43/crates/tensorfs-core/src/checkpoint_root.rs +314 -0
  8. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/gc.rs +8 -2
  9. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/header.rs +8 -0
  10. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/convert.rs +40 -2
  11. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/fingerprint.rs +5 -2
  12. tensorfs-0.3.43/crates/tensorfs-core/src/ingest/h3_lora.rs +267 -0
  13. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/mod.rs +6 -1
  14. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/source.rs +19 -3
  15. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/lib.rs +1 -0
  16. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/storage.rs +37 -11
  17. tensorfs-0.3.43/crates/tensorfs-core/tests/checkpoint_roots.rs +397 -0
  18. tensorfs-0.3.43/crates/tensorfs-core/tests/h3_lora.rs +451 -0
  19. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/source_plan.rs +3 -1
  20. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/source_prefixes.rs +56 -0
  21. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/src/store.rs +76 -0
  22. {tensorfs-0.3.41 → tensorfs-0.3.43}/pyproject.toml +1 -1
  23. {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/_derived_channel.py +1 -1
  24. {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/_ext.pyi +17 -0
  25. tensorfs-0.3.43/python/tensorfs/_source_channel.py +190 -0
  26. {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/derived.py +24 -1
  27. tensorfs-0.3.41/python/tensorfs/_source_channel.py +0 -105
  28. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/build.rs +0 -0
  29. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  30. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  31. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  32. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  33. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/b64.rs +0 -0
  34. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  35. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  36. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  37. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
  38. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  39. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
  40. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  41. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  42. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/cache_roots.rs +0 -0
  43. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/canon.rs +0 -0
  44. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/capability.rs +0 -0
  45. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/catalog.rs +0 -0
  46. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  47. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/corpus.rs +0 -0
  48. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  49. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  50. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  51. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  52. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
  53. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/retention.rs +0 -0
  54. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived/roots.rs +0 -0
  55. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/derived.rs +0 -0
  56. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/dtype.rs +0 -0
  57. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/durability.rs +0 -0
  58. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/err.rs +0 -0
  59. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/fetch.rs +0 -0
  60. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/fill.rs +0 -0
  61. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/fit.rs +0 -0
  62. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/home.rs +0 -0
  63. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ids.rs +0 -0
  64. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  65. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  66. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  67. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
  68. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  69. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
  70. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/jcs.rs +0 -0
  71. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/limits.rs +0 -0
  72. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/machine.rs +0 -0
  73. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/manifest.rs +0 -0
  74. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/meta.rs +0 -0
  75. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/project.rs +0 -0
  76. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/providers.rs +0 -0
  77. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/read.rs +0 -0
  78. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/receipt.rs +0 -0
  79. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/reclaim.rs +0 -0
  80. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/registry.rs +0 -0
  81. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/relation.rs +0 -0
  82. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/repo_cache.rs +0 -0
  83. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/repository.rs +0 -0
  84. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/sha256.rs +0 -0
  85. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/source_artifact.rs +0 -0
  86. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/spec.rs +0 -0
  87. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/staging.rs +0 -0
  88. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/store.rs +0 -0
  89. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/api.rs +0 -0
  90. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/http.rs +0 -0
  91. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/hub.rs +0 -0
  92. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  93. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  94. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  95. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
  96. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/pull.rs +0 -0
  97. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/push.rs +0 -0
  98. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
  99. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/transport/tests.rs +0 -0
  100. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/src/vectors.rs +0 -0
  101. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  102. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fit.rs +0 -0
  103. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  104. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  105. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  106. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  107. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  108. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  109. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  110. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  111. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/home.rs +0 -0
  112. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  113. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  114. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/liveness.rs +0 -0
  115. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/read_plan.rs +0 -0
  116. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  117. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  118. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  119. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  120. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  121. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  122. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  123. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  124. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
  125. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/staging.rs +0 -0
  126. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
  127. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  128. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/version.rs +0 -0
  129. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-core/tests/write_through.rs +0 -0
  130. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/Cargo.toml +0 -0
  131. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/src/docs.rs +0 -0
  132. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/src/errors.rs +0 -0
  133. {tensorfs-0.3.41 → tensorfs-0.3.43}/crates/tensorfs-py/src/lib.rs +0 -0
  134. {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/__init__.py +0 -0
  135. {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/errors.py +0 -0
  136. {tensorfs-0.3.41 → tensorfs-0.3.43}/python/tensorfs/py.typed +0 -0
@@ -421,9 +421,10 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
421
421
 
422
422
  [[package]]
423
423
  name = "tensorfs-core"
424
- version = "0.3.41"
424
+ version = "0.3.43"
425
425
  dependencies = [
426
426
  "fs2",
427
+ "libc",
427
428
  "minicbor",
428
429
  "rcgen",
429
430
  "rusqlite",
@@ -434,7 +435,7 @@ dependencies = [
434
435
 
435
436
  [[package]]
436
437
  name = "tensorfs-py"
437
- version = "0.3.41"
438
+ version = "0.3.43"
438
439
  dependencies = [
439
440
  "pyo3",
440
441
  "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.41"
6
+ version = "0.3.43"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.41
3
+ Version: 0.3.43
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
@@ -18,4 +18,5 @@ rustls = { version = "0.23", default-features = false, features = ["ring", "std"
18
18
  webpki-roots = "0.26"
19
19
 
20
20
  [dev-dependencies]
21
+ libc = "0.2"
21
22
  rcgen = "0.13"
@@ -1506,6 +1506,27 @@ pub fn cmd_worker(root: &Path, target: &str, f: &Flags) -> ExitCode {
1506
1506
  let build = tensorfs_core::ids::object_id(
1507
1507
  format!("{}|{}", p.conv.name, env!("CARGO_PKG_VERSION")).as_bytes(),
1508
1508
  );
1509
+ let source_bytes = match p.files.iter().try_fold(0u64, |total, source| {
1510
+ total.checked_add(source.header.file_len)
1511
+ }) {
1512
+ Some(bytes) => bytes,
1513
+ None => {
1514
+ return bail(tensorfs_core::err::Refusal {
1515
+ code: Code::ARITH_OVERFLOW,
1516
+ detail: "conversion input byte count overflow".into(),
1517
+ })
1518
+ }
1519
+ };
1520
+ let logical_output_bytes = ok!(p.plan.declared_bytes());
1521
+ let logical_output_observation = match i64::try_from(logical_output_bytes) {
1522
+ Ok(bytes) => bytes,
1523
+ Err(_) => {
1524
+ return bail(tensorfs_core::err::Refusal {
1525
+ code: Code::ARITH_OVERFLOW,
1526
+ detail: "conversion output exceeds signed accounting extent".into(),
1527
+ })
1528
+ }
1529
+ };
1509
1530
  let profile = IngestProfile {
1510
1531
  class: "normal".to_string(),
1511
1532
  source_dialect: p.conv.dialect.to_string(),
@@ -1518,7 +1539,10 @@ pub fn cmd_worker(root: &Path, target: &str, f: &Flags) -> ExitCode {
1518
1539
  v.dedup_by(|a, b| a.sha256 == b.sha256);
1519
1540
  v
1520
1541
  },
1521
- max_source_bytes: p.files.iter().map(|s| s.header.file_len).sum(),
1542
+ // The receipt quota counts materialized bytes. A reviewed permutation can
1543
+ // duplicate a low-rank factor into several canonical projections; its exact
1544
+ // planned output, not only the carrier length, must fit the same bound.
1545
+ max_source_bytes: source_bytes.max(logical_output_bytes),
1522
1546
  max_tensors: limits::MAX_TENSORS as u64,
1523
1547
  };
1524
1548
  ok!(profile.validate(&registry::platform_digests()));
@@ -1547,6 +1571,10 @@ pub fn cmd_worker(root: &Path, target: &str, f: &Flags) -> ExitCode {
1547
1571
  let isolation_tier: u64 = flag_num(f, "isolation-tier", 1);
1548
1572
  out.obs
1549
1573
  .push(("source_bytes".to_string(), profile.max_source_bytes as i64));
1574
+ out.obs.push((
1575
+ "logical_output_bytes".to_string(),
1576
+ logical_output_observation,
1577
+ ));
1550
1578
  out.obs.push(("golden_cases".to_string(), golden as i64));
1551
1579
  out.obs
1552
1580
  .push(("isolation_tier".to_string(), isolation_tier as i64));
@@ -436,6 +436,26 @@ fn encode_part(e: &mut Encoder<Bounded>, role: &str, part: &Part) -> Result<()>
436
436
  Ok(())
437
437
  }
438
438
 
439
+ pub(crate) fn encoded_tensor_identity(tensor: &Tensor) -> Result<String> {
440
+ let mut encoder = Encoder::new(Bounded { bytes: Vec::new() });
441
+ emit("tensor identity", encoder.array(5))?;
442
+ emit(
443
+ "tensor identity domain",
444
+ encoder.str("tensorfs.encoded-tensor/1"),
445
+ )?;
446
+ emit("tensor identity dtype", encoder.u8(tensor.dtype.code()))?;
447
+ encode_shape(&mut encoder, &tensor.shape)?;
448
+ emit("tensor identity encoding", encoder.str(&tensor.encoding))?;
449
+ emit(
450
+ "tensor identity parts",
451
+ encoder.array(tensor.parts.len() as u64),
452
+ )?;
453
+ for (role, part) in &tensor.parts {
454
+ encode_part(&mut encoder, role, part)?;
455
+ }
456
+ Ok(crate::ids::object_id(&encoder.into_writer().bytes))
457
+ }
458
+
439
459
  fn decode_part(d: &mut Decoder<'_>, what: &str) -> Result<(String, Part)> {
440
460
  exact_array(d, what, 4)?;
441
461
  let role = text(d, &format!("{what}.role"), limits::MAX_NAME_BYTES)?;
@@ -0,0 +1,314 @@
1
+ //! Independent input custody for an exact, already-materialized repository checkpoint.
2
+ //! These filesystem roots retain bytes; they do not claim a computation or issue a receipt.
3
+
4
+ use std::fs::{self, File, OpenOptions};
5
+ use std::io::{Read, Write};
6
+ use std::os::unix::fs::OpenOptionsExt;
7
+ use std::path::PathBuf;
8
+
9
+ use fs2::FileExt;
10
+
11
+ use crate::canon::{Fields, Value};
12
+ use crate::catalog::WriterGuard;
13
+ use crate::err::{refuse, Code, Refusal, Result};
14
+ use crate::ids::{hex64, prefixed, Doc, ObjectRef, Plain};
15
+ use crate::repository::{Repository, RepositoryName};
16
+ use crate::storage::HeldKey;
17
+ use crate::store::{Store, O_NOFOLLOW};
18
+
19
+ #[derive(Clone, Debug, PartialEq, Eq)]
20
+ pub struct CheckpointRoot {
21
+ pub owner: String,
22
+ pub repository: RepositoryName,
23
+ pub manifest: ObjectRef,
24
+ pub released: bool,
25
+ }
26
+
27
+ impl Plain for CheckpointRoot {
28
+ const MAX_BYTES: usize = 4096;
29
+
30
+ fn from_value(value: &Value) -> Result<Self> {
31
+ let mut fields = Fields::new("CheckpointRoot", value)?;
32
+ let owner = prefixed("checkpoint root owner", fields.req_str("owner")?)?;
33
+ let repository = repository(fields.req_str("repository")?)?;
34
+ let manifest = ObjectRef::from_value("checkpoint root manifest", fields.req("manifest")?)?;
35
+ let released = match fields.req("released")? {
36
+ Value::Bool(value) => *value,
37
+ _ => return refuse(Code::WRONG_TYPE, "checkpoint root release must be boolean"),
38
+ };
39
+ fields.done()?;
40
+ if manifest.length == 0 || manifest.length > crate::limits::INT_MAX as u64 {
41
+ return refuse(Code::LENGTH_MISMATCH, "checkpoint root manifest is empty");
42
+ }
43
+ Ok(Self {
44
+ owner,
45
+ repository,
46
+ manifest,
47
+ released,
48
+ })
49
+ }
50
+
51
+ fn to_value(&self) -> Value {
52
+ Value::obj(vec![
53
+ ("owner", Value::str(self.owner.clone())),
54
+ (
55
+ "repository",
56
+ Value::str(format!("{}/{}", self.repository.org, self.repository.name)),
57
+ ),
58
+ ("manifest", self.manifest.to_value()),
59
+ ("released", Value::Bool(self.released)),
60
+ ])
61
+ }
62
+ }
63
+
64
+ pub fn repository(value: &str) -> Result<RepositoryName> {
65
+ let (org, name) = value.split_once('/').ok_or_else(|| Refusal {
66
+ code: Code::KEY_GRAMMAR,
67
+ detail: "checkpoint source repository must be org/name".into(),
68
+ })?;
69
+ RepositoryName::new(org, name)
70
+ }
71
+
72
+ fn io(error: std::io::Error) -> Refusal {
73
+ Refusal {
74
+ code: Code::IO_FAILED,
75
+ detail: format!("checkpoint root: {error}"),
76
+ }
77
+ }
78
+
79
+ fn directory(store: &Store) -> PathBuf {
80
+ store.root().join("roots/checkpoints")
81
+ }
82
+
83
+ fn path(store: &Store, owner: &str) -> Result<PathBuf> {
84
+ prefixed("checkpoint root owner", owner)?;
85
+ Ok(directory(store).join(format!("{}.json", &owner[7..])))
86
+ }
87
+
88
+ /// Inspect the native root, including its permanent release tombstone.
89
+ pub fn read(store: &Store, owner: &str) -> Result<Option<CheckpointRoot>> {
90
+ let file = match OpenOptions::new()
91
+ .read(true)
92
+ .custom_flags(O_NOFOLLOW)
93
+ .open(path(store, owner)?)
94
+ {
95
+ Ok(file) => file,
96
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
97
+ Err(error) => return Err(io(error)),
98
+ };
99
+ let mut bytes = Vec::new();
100
+ file.take(CheckpointRoot::MAX_BYTES as u64 + 1)
101
+ .read_to_end(&mut bytes)
102
+ .map_err(io)?;
103
+ let root = CheckpointRoot::parse(&bytes)?;
104
+ if root.owner != owner {
105
+ return refuse(
106
+ Code::CROSS_SUBJECT_REPLAY,
107
+ "checkpoint root path names another owner",
108
+ );
109
+ }
110
+ Ok(Some(root))
111
+ }
112
+
113
+ fn lock(store: &Store, owner: &str) -> Result<File> {
114
+ let path = path(store, owner)?.with_extension("lock");
115
+ fs::create_dir_all(directory(store)).map_err(io)?;
116
+ let file = OpenOptions::new()
117
+ .read(true)
118
+ .write(true)
119
+ .create(true)
120
+ .truncate(false)
121
+ .custom_flags(O_NOFOLLOW)
122
+ .open(path)
123
+ .map_err(io)?;
124
+ file.lock_exclusive().map_err(io)?;
125
+ Ok(file)
126
+ }
127
+
128
+ fn write(store: &Store, root: &CheckpointRoot) -> Result<()> {
129
+ let path = path(store, &root.owner)?;
130
+ let temporary = path.with_extension(format!("tmp-{}", crate::meta::now_nanos_unique()));
131
+ let result = (|| {
132
+ let mut file = OpenOptions::new()
133
+ .write(true)
134
+ .create_new(true)
135
+ .open(&temporary)
136
+ .map_err(io)?;
137
+ file.write_all(&root.canonical_bytes())
138
+ .and_then(|()| file.sync_all())
139
+ .map_err(io)?;
140
+ fs::rename(&temporary, path).map_err(io)?;
141
+ for directory in [
142
+ directory(store),
143
+ store.root().join("roots"),
144
+ store.root().to_path_buf(),
145
+ ] {
146
+ File::open(directory)
147
+ .and_then(|file| file.sync_all())
148
+ .map_err(io)?;
149
+ }
150
+ Ok(())
151
+ })();
152
+ let _ = fs::remove_file(temporary);
153
+ result
154
+ }
155
+
156
+ fn identity(owner: &str, source: &str, manifest: ObjectRef) -> Result<CheckpointRoot> {
157
+ hex64("checkpoint root manifest", &manifest.sha256)?;
158
+ if manifest.length == 0 || manifest.length > crate::limits::INT_MAX as u64 {
159
+ return refuse(Code::LENGTH_MISMATCH, "checkpoint root manifest is empty");
160
+ }
161
+ Ok(CheckpointRoot {
162
+ owner: prefixed("checkpoint root owner", owner)?,
163
+ repository: repository(source)?,
164
+ manifest,
165
+ released: false,
166
+ })
167
+ }
168
+
169
+ fn same_subject(root: &CheckpointRoot, wanted: &CheckpointRoot) -> Result<()> {
170
+ if root.repository != wanted.repository || root.manifest != wanted.manifest {
171
+ return refuse(
172
+ Code::CROSS_SUBJECT_REPLAY,
173
+ "checkpoint root already names another subject",
174
+ );
175
+ }
176
+ Ok(())
177
+ }
178
+
179
+ fn verify(store: &Store, manifest: &ObjectRef) -> Result<()> {
180
+ let model = store.read_manifest(manifest)?;
181
+ let closure = crate::checkpoint::walk_cozytensors(store, &model)?;
182
+ closure.require_resident(store)?;
183
+ for object in closure.distinct() {
184
+ let record = store.record_valid(&object.sha256).map_err(|why| Refusal {
185
+ code: Code::OBJECT_CORRUPT,
186
+ detail: format!("checkpoint root closure has no current verification record: {why}"),
187
+ })?;
188
+ if record.length != object.length {
189
+ return refuse(
190
+ Code::LENGTH_MISMATCH,
191
+ "checkpoint root closure record changed length",
192
+ );
193
+ }
194
+ }
195
+ Ok(())
196
+ }
197
+
198
+ /// Retain the runtime closure of an exact native repository checkpoint.
199
+ pub fn retain(
200
+ store: &Store,
201
+ owner: &str,
202
+ source: &str,
203
+ manifest: ObjectRef,
204
+ ) -> Result<CheckpointRoot> {
205
+ let wanted = identity(owner, source, manifest)?;
206
+ let _gc = WriterGuard::acquire(store.root())?;
207
+ let _owner = lock(store, owner)?;
208
+ if let Some(root) = read(store, owner)? {
209
+ same_subject(&root, &wanted)?;
210
+ if root.released {
211
+ return refuse(
212
+ Code::TRANSACTION_CLOSED,
213
+ "checkpoint root was permanently released",
214
+ );
215
+ }
216
+ verify(store, &root.manifest)?;
217
+ return Ok(root);
218
+ }
219
+ verify_source(store, &wanted.repository, &wanted.manifest)?;
220
+ write(store, &wanted)?;
221
+ Ok(wanted)
222
+ }
223
+
224
+ /// Validate all source inputs before the execution owner creates any recipient roots.
225
+ pub fn check_source(store: &Store, source: &str, manifest: &ObjectRef) -> Result<()> {
226
+ let repository = repository(source)?;
227
+ let _gc = WriterGuard::acquire(store.root())?;
228
+ verify_source(store, &repository, manifest)
229
+ }
230
+
231
+ fn verify_source(store: &Store, repository: &RepositoryName, manifest: &ObjectRef) -> Result<()> {
232
+ let file = OpenOptions::new()
233
+ .read(true)
234
+ .custom_flags(O_NOFOLLOW)
235
+ .open(store.repository_path(repository))
236
+ .map_err(|error| {
237
+ if error.kind() == std::io::ErrorKind::NotFound {
238
+ Refusal {
239
+ code: Code::REPOSITORY_ABSENT,
240
+ detail: "checkpoint source repository is absent".into(),
241
+ }
242
+ } else {
243
+ io(error)
244
+ }
245
+ })?;
246
+ let mut body = Vec::new();
247
+ file.take(Repository::MAX_BYTES as u64 + 1)
248
+ .read_to_end(&mut body)
249
+ .map_err(io)?;
250
+ let actual = Repository::parse(&body)?;
251
+ if &actual.repo != repository
252
+ || !actual
253
+ .checkpoints
254
+ .iter()
255
+ .any(|row| &row.manifest == manifest)
256
+ {
257
+ return refuse(
258
+ Code::ROOT_ABSENT,
259
+ "checkpoint has no exact source repository membership",
260
+ );
261
+ }
262
+ verify(store, manifest)
263
+ }
264
+
265
+ /// Release this exact owner permanently, even before its first acquisition.
266
+ pub fn release(
267
+ store: &Store,
268
+ owner: &str,
269
+ source: &str,
270
+ manifest: ObjectRef,
271
+ ) -> Result<CheckpointRoot> {
272
+ let mut wanted = identity(owner, source, manifest)?;
273
+ let _gc = WriterGuard::acquire(store.root())?;
274
+ let _owner = lock(store, owner)?;
275
+ if let Some(root) = read(store, owner)? {
276
+ same_subject(&root, &wanted)?;
277
+ if root.released {
278
+ return Ok(root);
279
+ }
280
+ }
281
+ wanted.released = true;
282
+ write(store, &wanted)?;
283
+ Ok(wanted)
284
+ }
285
+
286
+ pub(crate) fn held_objects(store: &Store) -> Result<Vec<HeldKey>> {
287
+ let entries = match fs::read_dir(directory(store)) {
288
+ Ok(entries) => entries,
289
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
290
+ Err(error) => return Err(io(error)),
291
+ };
292
+ let mut objects = Vec::new();
293
+ for entry in entries {
294
+ let entry = entry.map_err(io)?;
295
+ let name = entry.file_name();
296
+ let name = name.to_string_lossy();
297
+ let Some(hex) = name.strip_suffix(".json") else {
298
+ continue;
299
+ };
300
+ let root = read(store, &format!("sha256:{hex}"))?.ok_or_else(|| Refusal {
301
+ code: Code::ROOT_ABSENT,
302
+ detail: "checkpoint root disappeared during census".into(),
303
+ })?;
304
+ if !root.released {
305
+ objects.push(HeldKey {
306
+ key: crate::storage::manifest_key(&root.manifest.sha256)?,
307
+ kind: "cozytensors".into(),
308
+ length: root.manifest.length,
309
+ sha256: root.manifest.sha256,
310
+ });
311
+ }
312
+ }
313
+ Ok(objects)
314
+ }
@@ -134,7 +134,13 @@ pub fn session_holds(store: &Store) -> Result<(Vec<HeldKey>, Vec<String>)> {
134
134
  .collect::<Result<Vec<_>>>()?;
135
135
  holds.extend(crate::derived::retained_objects(store)?);
136
136
  holds.extend(crate::source_artifact::held_objects(store)?);
137
- holds.sort_by(|left, right| left.key.cmp(&right.key));
137
+ holds.extend(crate::checkpoint_root::held_objects(store)?);
138
+ // A full snapshot obligation wins over a runtime-only pin of the same manifest.
139
+ holds.sort_by(|left, right| {
140
+ left.key
141
+ .cmp(&right.key)
142
+ .then_with(|| (left.kind == "cozytensors").cmp(&(right.kind == "cozytensors")))
143
+ });
138
144
  if holds
139
145
  .windows(2)
140
146
  .any(|pair| pair[0].key == pair[1].key && pair[0].length != pair[1].length)
@@ -165,7 +171,7 @@ pub(crate) fn retained_manifests(store: &Store) -> Result<Vec<crate::ids::Object
165
171
  roots.extend(
166
172
  holds
167
173
  .into_iter()
168
- .filter(|hold| hold.kind == "manifest")
174
+ .filter(|hold| hold.is_manifest())
169
175
  .map(|hold| crate::ids::ObjectRef {
170
176
  sha256: hold.sha256,
171
177
  length: hold.length,
@@ -93,6 +93,14 @@ pub struct Tensor {
93
93
  pub parts: Vec<(String, Part)>,
94
94
  }
95
95
 
96
+ impl Tensor {
97
+ /// Opaque equality of encoded geometry and bytes, independent of tensor names
98
+ /// and unrelated header configs or encodings. This is not a Store capability.
99
+ pub fn encoded_identity(&self) -> Result<String> {
100
+ crate::cbor::encoded_tensor_identity(self)
101
+ }
102
+ }
103
+
96
104
  #[derive(Debug, Clone, PartialEq, Eq)]
97
105
  pub struct Asset {
98
106
  pub logical_sha256: String,
@@ -53,7 +53,7 @@ pub struct Converter {
53
53
  pub validated_on_real_bytes: bool,
54
54
  }
55
55
 
56
- pub const CONVERTERS: [Converter; 4] = [
56
+ pub const CONVERTERS: [Converter; 5] = [
57
57
  Converter {
58
58
  name: "diffusers.identity/1",
59
59
  dialect: "safetensors.diffusers",
@@ -79,6 +79,13 @@ pub const CONVERTERS: [Converter; 4] = [
79
79
  REFERENCE; nothing is re-hashed and nothing is re-uploaded.",
80
80
  validated_on_real_bytes: true,
81
81
  },
82
+ Converter {
83
+ name: "h3.lora/1",
84
+ dialect: "safetensors.h3_lora",
85
+ class: Class::Permutation,
86
+ doc: "Native H3 LoRA A/B and Kohya down/up factors: split head-interleaved QKV B rows, swap SwiGLU B halves, preserve A and alpha without arithmetic.",
87
+ validated_on_real_bytes: false,
88
+ },
82
89
  Converter {
83
90
  name: "h3.native/2",
84
91
  dialect: "safetensors.h3_native",
@@ -376,7 +383,12 @@ impl Plan {
376
383
  let mut n = 0u64;
377
384
  for o in &self.ops {
378
385
  for r in &o.roles {
379
- n += checked_bytes(&o.out_key, &r.shape, r.dtype)?;
386
+ n = n
387
+ .checked_add(checked_bytes(&o.out_key, &r.shape, r.dtype)?)
388
+ .ok_or_else(|| crate::err::Refusal {
389
+ code: Code::ARITH_OVERFLOW,
390
+ detail: "conversion output byte count overflow".into(),
391
+ })?;
380
392
  }
381
393
  }
382
394
  Ok(n)
@@ -947,6 +959,15 @@ fn plan_carrier(
947
959
  cands: &Candidates<'_>,
948
960
  p: &mut Plan,
949
961
  ) -> Result<()> {
962
+ if conv.name == "h3.lora/1" {
963
+ if allowed != ["plain/1"] {
964
+ return refuse(
965
+ Code::UNKNOWN_ENCODING,
966
+ "H3 LoRA ingestion preserves floating point factors in plain/1",
967
+ );
968
+ }
969
+ return super::h3_lora::plan(component, file, h, cands.plain, p);
970
+ }
950
971
  let alias = Allowed(allowed);
951
972
  p.header_bytes += h.header_bytes;
952
973
  let plain_map = role_map("plain/1")?;
@@ -1738,6 +1759,23 @@ pub fn h3_geometry(
1738
1759
  /// error at ~90% numerically while nothing raised.
1739
1760
  pub fn golden_cases(c: &Converter) -> Vec<(&'static str, Xform)> {
1740
1761
  match c.name {
1762
+ "h3.lora/1" => vec![
1763
+ (
1764
+ "H3 LoRA SwiGLU B rows",
1765
+ Xform::SwapHalves {
1766
+ half_bytes: 14336 * 16 * 2,
1767
+ },
1768
+ ),
1769
+ (
1770
+ "H3 LoRA QKV B rows",
1771
+ Xform::QkvSplit {
1772
+ groups: H3_HEADS,
1773
+ shares: [1, 1, 1],
1774
+ take: 0,
1775
+ unit_bytes: H3_HEAD_DIM * 16 * 2,
1776
+ },
1777
+ ),
1778
+ ],
1741
1779
  "h3.native/2" => vec![
1742
1780
  (
1743
1781
  "h3-swiglu gate/value rows",
@@ -855,7 +855,9 @@ pub fn validate_source_member(member: &str) -> Result<()> {
855
855
  }
856
856
 
857
857
  pub fn validate_source_member_prefix(prefix: &str) -> Result<()> {
858
- if !prefix.ends_with('/') || validate_source_member(&format!("{prefix}member")).is_err() {
858
+ if !prefix.is_empty()
859
+ && (!prefix.ends_with('/') || validate_source_member(&format!("{prefix}member")).is_err())
860
+ {
859
861
  return refuse(
860
862
  Code::KEY_GRAMMAR,
861
863
  format!("source member prefix {prefix:?} is not a safe canonical prefix"),
@@ -867,8 +869,9 @@ pub fn validate_source_member_prefix(prefix: &str) -> Result<()> {
867
869
  /// Decoded `__metadata__` facts the border is willing to carry as EVIDENCE STAMPS. Foreign
868
870
  /// JSON is decoded through this strict list or discarded — it never survives as itself.
869
871
  pub fn evidence_stamps(h: &SourceHeader) -> Result<Vec<(String, String)>> {
870
- const KEPT: [&str; 4] = [
872
+ const KEPT: [&str; 5] = [
871
873
  "modelspec.architecture",
874
+ "ss_h3_training_mode",
872
875
  "modelspec.prediction_type",
873
876
  "format",
874
877
  "scaled_fp8",