tensorfs 0.3.44__tar.gz → 0.3.45__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 (137) hide show
  1. {tensorfs-0.3.44 → tensorfs-0.3.45}/Cargo.lock +2 -2
  2. {tensorfs-0.3.44 → tensorfs-0.3.45}/Cargo.toml +1 -1
  3. {tensorfs-0.3.44 → tensorfs-0.3.45}/PKG-INFO +1 -1
  4. tensorfs-0.3.45/crates/tensorfs-core/src/asset_update.rs +293 -0
  5. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/lib.rs +1 -0
  6. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-py/src/store.rs +37 -0
  7. {tensorfs-0.3.44 → tensorfs-0.3.45}/pyproject.toml +1 -1
  8. {tensorfs-0.3.44 → tensorfs-0.3.45}/python/tensorfs/_ext.pyi +7 -0
  9. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/Cargo.toml +0 -0
  10. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/build.rs +0 -0
  11. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  12. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  13. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  14. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  15. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/b64.rs +0 -0
  16. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  17. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  18. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  19. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
  20. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
  21. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  22. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
  23. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  24. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  25. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/cache_roots.rs +0 -0
  26. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/canon.rs +0 -0
  27. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/capability.rs +0 -0
  28. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/catalog.rs +0 -0
  29. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/cbor.rs +0 -0
  30. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  31. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
  32. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/corpus.rs +0 -0
  33. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  34. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  35. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  36. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  37. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
  38. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/derived/retention.rs +0 -0
  39. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/derived/roots.rs +0 -0
  40. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/derived.rs +0 -0
  41. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/dtype.rs +0 -0
  42. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/durability.rs +0 -0
  43. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/err.rs +0 -0
  44. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/fetch.rs +0 -0
  45. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/fill.rs +0 -0
  46. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/fit.rs +0 -0
  47. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/gc.rs +0 -0
  48. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/header.rs +0 -0
  49. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/home.rs +0 -0
  50. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ids.rs +0 -0
  51. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  52. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
  53. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
  54. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  55. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
  56. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  57. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  58. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
  59. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/source.rs +0 -0
  60. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  61. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
  62. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/jcs.rs +0 -0
  63. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/limits.rs +0 -0
  64. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/machine.rs +0 -0
  65. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/manifest.rs +0 -0
  66. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/meta.rs +0 -0
  67. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/project.rs +0 -0
  68. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/providers.rs +0 -0
  69. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/read.rs +0 -0
  70. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/receipt.rs +0 -0
  71. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/reclaim.rs +0 -0
  72. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/registry.rs +0 -0
  73. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/relation.rs +0 -0
  74. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/repo_cache.rs +0 -0
  75. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/repository.rs +0 -0
  76. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/sha256.rs +0 -0
  77. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/source_artifact.rs +0 -0
  78. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/spec.rs +0 -0
  79. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/staging.rs +0 -0
  80. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/storage.rs +0 -0
  81. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/store.rs +0 -0
  82. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/api.rs +0 -0
  83. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/http.rs +0 -0
  84. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/hub.rs +0 -0
  85. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  86. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  87. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  88. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
  89. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/pull.rs +0 -0
  90. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/push.rs +0 -0
  91. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
  92. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/transport/tests.rs +0 -0
  93. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/src/vectors.rs +0 -0
  94. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
  95. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  96. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fit.rs +0 -0
  97. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  98. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  99. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  100. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  101. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  102. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  103. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  104. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
  105. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  106. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/home.rs +0 -0
  107. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  108. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  109. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/liveness.rs +0 -0
  110. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/read_plan.rs +0 -0
  111. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  112. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  113. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  114. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  115. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  116. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  117. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  118. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  119. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  120. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
  121. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
  122. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/staging.rs +0 -0
  123. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
  124. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  125. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/version.rs +0 -0
  126. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-core/tests/write_through.rs +0 -0
  127. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-py/Cargo.toml +0 -0
  128. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-py/src/docs.rs +0 -0
  129. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-py/src/errors.rs +0 -0
  130. {tensorfs-0.3.44 → tensorfs-0.3.45}/crates/tensorfs-py/src/lib.rs +0 -0
  131. {tensorfs-0.3.44 → tensorfs-0.3.45}/python/tensorfs/__init__.py +0 -0
  132. {tensorfs-0.3.44 → tensorfs-0.3.45}/python/tensorfs/_derived_channel.py +0 -0
  133. {tensorfs-0.3.44 → tensorfs-0.3.45}/python/tensorfs/_source_channel.py +0 -0
  134. {tensorfs-0.3.44 → tensorfs-0.3.45}/python/tensorfs/_tensors.py +0 -0
  135. {tensorfs-0.3.44 → tensorfs-0.3.45}/python/tensorfs/derived.py +0 -0
  136. {tensorfs-0.3.44 → tensorfs-0.3.45}/python/tensorfs/errors.py +0 -0
  137. {tensorfs-0.3.44 → tensorfs-0.3.45}/python/tensorfs/py.typed +0 -0
@@ -421,7 +421,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
421
421
 
422
422
  [[package]]
423
423
  name = "tensorfs-core"
424
- version = "0.3.44"
424
+ version = "0.3.45"
425
425
  dependencies = [
426
426
  "fs2",
427
427
  "libc",
@@ -435,7 +435,7 @@ dependencies = [
435
435
 
436
436
  [[package]]
437
437
  name = "tensorfs-py"
438
- version = "0.3.44"
438
+ version = "0.3.45"
439
439
  dependencies = [
440
440
  "pyo3",
441
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.44"
6
+ version = "0.3.45"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.44
3
+ Version: 0.3.45
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
@@ -0,0 +1,293 @@
1
+ //! Metadata-only model-asset updates.
2
+ //!
3
+ //! An asset update rewrites only the CozyTensors header and manifest. Tensor part
4
+ //! ObjectRefs are copied verbatim; asset source objects are selected from an existing
5
+ //! manifest and are never opened or rehashed by this operation. Payload verification
6
+ //! remains the normal `read_asset` responsibility (or the repository admission gate).
7
+
8
+ use std::collections::BTreeMap;
9
+
10
+ use crate::checkpoint;
11
+ use crate::err::{refuse, Code, Refusal, Result};
12
+ use crate::header::Asset;
13
+ use crate::ids::ObjectRef;
14
+ use crate::manifest::{Entry, Manifest};
15
+ use crate::store::Store;
16
+
17
+ #[derive(Debug, Clone, PartialEq, Eq)]
18
+ pub enum Edit {
19
+ /// Add or replace one named asset from an exact ordinary-file entry in the source
20
+ /// manifest. The source ObjectRef becomes the sole segment; no payload is read.
21
+ Copy {
22
+ name: String,
23
+ source_path: String,
24
+ media_type: String,
25
+ },
26
+ /// Remove an asset by its exact header name. Missing names are harmless, making
27
+ /// retries deterministic.
28
+ Drop { name: String },
29
+ }
30
+
31
+ #[derive(Debug, Clone, PartialEq, Eq)]
32
+ pub struct ResultSnapshot {
33
+ pub header: ObjectRef,
34
+ pub manifest: ObjectRef,
35
+ }
36
+
37
+ fn source_object(manifest: &Manifest, path: &str) -> Result<ObjectRef> {
38
+ manifest
39
+ .entries()
40
+ .iter()
41
+ .find_map(|(candidate, entry)| {
42
+ (candidate == path).then(|| match entry {
43
+ Entry::File(object) => Ok(object.clone()),
44
+ Entry::CozyTensors(_) => Err(Refusal {
45
+ code: Code::WRONG_TYPE,
46
+ detail: format!("asset source {path:?} is a CozyTensors header, not a file"),
47
+ }),
48
+ })
49
+ })
50
+ .unwrap_or_else(|| {
51
+ Err(Refusal {
52
+ code: Code::OBJECT_ABSENT,
53
+ detail: format!("asset source {path:?} is absent from the source manifest"),
54
+ })
55
+ })
56
+ }
57
+
58
+ /// Apply add/replace/drop edits to one existing checkpoint without reading tensor payloads.
59
+ ///
60
+ /// The source manifest and header are read, and each selected source object is checked only
61
+ /// through its existing verification record and length. The new header and manifest are the
62
+ /// only bytes written. Callers that need a payload guarantee should subsequently use
63
+ /// [`crate::checkpoint::read_asset`], which hashes the reassembled asset once.
64
+ pub fn update(
65
+ store: &Store,
66
+ source_manifest: &ObjectRef,
67
+ edits: &[Edit],
68
+ ) -> Result<ResultSnapshot> {
69
+ let source = checkpoint::load_manifest(store, source_manifest)?;
70
+ let header_ref = source.header().ok_or_else(|| Refusal {
71
+ code: Code::ATTACHMENT_CARDINALITY,
72
+ detail: "asset update source has no CozyTensors header".into(),
73
+ })?;
74
+ let mut header = checkpoint::load_header(store, header_ref)?;
75
+ let closure = checkpoint::load_closure(store, &header)?;
76
+
77
+ let mut assets: BTreeMap<String, Asset> = header.assets.drain(..).collect();
78
+ for edit in edits {
79
+ match edit {
80
+ Edit::Drop { name } => {
81
+ assets.remove(name);
82
+ }
83
+ Edit::Copy {
84
+ name,
85
+ source_path,
86
+ media_type,
87
+ } => {
88
+ if name.is_empty() || media_type.is_empty() {
89
+ return refuse(
90
+ Code::MISSING_FIELD,
91
+ "asset name and media_type are required",
92
+ );
93
+ }
94
+ let object = source_object(&source, source_path)?;
95
+ // This consults only the durable record's inode/length facts. It does not
96
+ // open or hash the payload, which is the defining property of this operation.
97
+ let record = store
98
+ .record_valid(&object.sha256)
99
+ .map_err(|detail| Refusal {
100
+ code: Code::OBJECT_CORRUPT,
101
+ detail: format!("asset source {} is not verified: {detail}", object.id()),
102
+ })?;
103
+ if record.length != object.length {
104
+ return refuse(
105
+ Code::LENGTH_MISMATCH,
106
+ format!("asset source {} record length differs", object.id()),
107
+ );
108
+ }
109
+ assets.insert(
110
+ name.clone(),
111
+ Asset {
112
+ logical_sha256: object.sha256.clone(),
113
+ logical_length: object.length,
114
+ media_type: media_type.clone(),
115
+ segments: vec![object],
116
+ },
117
+ );
118
+ }
119
+ }
120
+ }
121
+ header.assets = assets.into_iter().collect();
122
+ header.validate(&closure)?;
123
+ let header_object = checkpoint::put_doc(store, &header)?;
124
+ let manifest = checkpoint::build_manifest(&header, &header_object, &closure)?;
125
+ let manifest_object = store.put_manifest(&manifest)?.obj;
126
+ Ok(ResultSnapshot {
127
+ header: header_object,
128
+ manifest: manifest_object,
129
+ })
130
+ }
131
+
132
+ #[cfg(test)]
133
+ mod tests {
134
+ use super::*;
135
+ use crate::checkpoint;
136
+ use crate::header::{Body, Header, Part, Tensor};
137
+ use crate::manifest::Draft;
138
+ use crate::store::Fault;
139
+ use crate::{dtype::Dtype, registry};
140
+
141
+ fn temp() -> std::path::PathBuf {
142
+ std::env::temp_dir().join(format!(
143
+ "tensorfs-asset-update-{}",
144
+ crate::meta::now_nanos_unique()
145
+ ))
146
+ }
147
+
148
+ fn put(store: &Store, bytes: &[u8]) -> ObjectRef {
149
+ let expected = ObjectRef::of(bytes);
150
+ let mut input = bytes;
151
+ store
152
+ .put_stream(&mut input, Some(&expected), &Fault::default())
153
+ .unwrap()
154
+ .obj
155
+ }
156
+
157
+ #[test]
158
+ fn copies_asset_refs_without_changing_tensor_refs_and_read_asset_verifies_bytes() {
159
+ let root = temp();
160
+ let store = Store::init(&root).unwrap();
161
+ let spec = registry::seeds()
162
+ .into_iter()
163
+ .find(|seed| seed.alias == "plain/1")
164
+ .unwrap()
165
+ .spec;
166
+ let header = Header {
167
+ configs: vec![],
168
+ assets: vec![],
169
+ encodings: vec![spec.clone()],
170
+ components: vec![(
171
+ "model".into(),
172
+ vec![(
173
+ "weight".into(),
174
+ Tensor {
175
+ dtype: Dtype::U8,
176
+ shape: vec![3],
177
+ encoding: spec.object_id(),
178
+ parts: vec![(
179
+ "value".into(),
180
+ Part {
181
+ dtype: Dtype::U8,
182
+ shape: vec![3],
183
+ body: Body::Inline(vec![1, 2, 3]),
184
+ },
185
+ )],
186
+ },
187
+ )],
188
+ )],
189
+ };
190
+ let header_ref = put(&store, &header.canonical_bytes().unwrap());
191
+ let asset_bytes = b"tokenizer bytes";
192
+ let asset_ref = put(&store, asset_bytes);
193
+ let source = Draft {
194
+ entries: vec![
195
+ ("model.cozytensors".into(), Entry::CozyTensors(header_ref)),
196
+ ("tokenizer/vocab.txt".into(), Entry::File(asset_ref.clone())),
197
+ ],
198
+ }
199
+ .seal()
200
+ .unwrap();
201
+ let source_ref = store.put_manifest(&source).unwrap().obj;
202
+
203
+ let result = update(
204
+ &store,
205
+ &source_ref,
206
+ &[Edit::Copy {
207
+ name: "tokenizer/vocab.txt".into(),
208
+ source_path: "tokenizer/vocab.txt".into(),
209
+ media_type: "text/plain".into(),
210
+ }],
211
+ )
212
+ .unwrap();
213
+ let output = checkpoint::load_header(&store, &result.header).unwrap();
214
+ assert_eq!(output.tensors().count(), 1);
215
+ assert_eq!(
216
+ output.tensors().next().unwrap().2.parts[0].1.body,
217
+ Body::Inline(vec![1, 2, 3])
218
+ );
219
+ let asset = &output.assets[0].1;
220
+ assert_eq!(asset.segments, vec![asset_ref]);
221
+ assert_eq!(asset.logical_length, asset_bytes.len() as u64);
222
+ let mut verified = Vec::new();
223
+ checkpoint::read_asset(&store, "tokenizer/vocab.txt", asset, &mut verified).unwrap();
224
+ assert_eq!(verified, asset_bytes);
225
+ std::fs::remove_dir_all(root).unwrap();
226
+ }
227
+
228
+ #[test]
229
+ fn drops_are_deterministic_and_do_not_touch_payloads() {
230
+ let root = temp();
231
+ let store = Store::init(&root).unwrap();
232
+ let spec = registry::seeds().into_iter().next().unwrap().spec;
233
+ let payload = put(&store, b"asset");
234
+ let header = Header {
235
+ configs: vec![],
236
+ assets: vec![(
237
+ "old".into(),
238
+ Asset {
239
+ logical_sha256: payload.sha256.clone(),
240
+ logical_length: payload.length,
241
+ media_type: "text/plain".into(),
242
+ segments: vec![payload.clone()],
243
+ },
244
+ )],
245
+ encodings: vec![spec.clone()],
246
+ components: vec![(
247
+ "model".into(),
248
+ vec![(
249
+ "w".into(),
250
+ Tensor {
251
+ dtype: Dtype::U8,
252
+ shape: vec![1],
253
+ encoding: spec.object_id(),
254
+ parts: vec![(
255
+ "value".into(),
256
+ Part {
257
+ dtype: Dtype::U8,
258
+ shape: vec![1],
259
+ body: Body::Inline(vec![0]),
260
+ },
261
+ )],
262
+ },
263
+ )],
264
+ )],
265
+ };
266
+ let href = put(&store, &header.canonical_bytes().unwrap());
267
+ let source = Draft {
268
+ entries: vec![("model.cozytensors".into(), Entry::CozyTensors(href))],
269
+ }
270
+ .seal()
271
+ .unwrap();
272
+ let source_ref = store.put_manifest(&source).unwrap().obj;
273
+ let one = update(
274
+ &store,
275
+ &source_ref,
276
+ &[Edit::Drop {
277
+ name: "missing".into(),
278
+ }],
279
+ )
280
+ .unwrap();
281
+ let two = update(
282
+ &store,
283
+ &source_ref,
284
+ &[Edit::Drop {
285
+ name: "missing".into(),
286
+ }],
287
+ )
288
+ .unwrap();
289
+ assert_eq!(one.header, two.header);
290
+ assert_eq!(one.manifest, two.manifest);
291
+ std::fs::remove_dir_all(root).unwrap();
292
+ }
293
+ }
@@ -8,6 +8,7 @@
8
8
  // crates/tensorfs-core/src is the check that it stays that way.
9
9
  #![deny(unsafe_code)]
10
10
 
11
+ pub mod asset_update;
11
12
  pub mod b64;
12
13
  mod cache_roots;
13
14
  pub mod canon;
@@ -591,6 +591,43 @@ impl PyStore {
591
591
  tensorfs_core::IMPLEMENTATION_SHA256
592
592
  }
593
593
 
594
+ /// Rewrite only a CozyTensors header/manifest to add, replace, or drop model assets.
595
+ /// `copies` are `(name, source_path, media_type)` rows selected from the exact source
596
+ /// manifest; TensorFS reuses their ObjectRefs without reading payload bytes.
597
+ #[pyo3(signature = (manifest_id, manifest_length, copies=Vec::new(), drops=Vec::new()))]
598
+ fn update_model_assets<'p>(
599
+ &self,
600
+ py: Python<'p>,
601
+ manifest_id: &str,
602
+ manifest_length: u64,
603
+ copies: Vec<(String, String, String)>,
604
+ drops: Vec<String>,
605
+ ) -> PyResult<Bound<'p, PyDict>> {
606
+ let source = obj_ref(py, manifest_id, manifest_length)?;
607
+ let mut edits = Vec::with_capacity(copies.len() + drops.len());
608
+ edits.extend(copies.into_iter().map(|(name, source_path, media_type)| {
609
+ tensorfs_core::asset_update::Edit::Copy {
610
+ name,
611
+ source_path,
612
+ media_type,
613
+ }
614
+ }));
615
+ edits.extend(
616
+ drops
617
+ .into_iter()
618
+ .map(|name| tensorfs_core::asset_update::Edit::Drop { name }),
619
+ );
620
+ let result = py
621
+ .detach(|| tensorfs_core::asset_update::update(&self.store, &source, &edits))
622
+ .or_refuse(py)?;
623
+ let out = PyDict::new(py);
624
+ out.set_item("header_id", result.header.id())?;
625
+ out.set_item("header_length", result.header.length)?;
626
+ out.set_item("manifest_id", result.manifest.id())?;
627
+ out.set_item("manifest_length", result.manifest.length)?;
628
+ Ok(out)
629
+ }
630
+
594
631
  fn verify_checkpoint_source(
595
632
  &self,
596
633
  py: Python<'_>,
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "tensorfs"
7
- version = "0.3.44"
7
+ version = "0.3.45"
8
8
  description = "TensorFS — canonical tensor artifact substrate"
9
9
  requires-python = ">=3.12"
10
10
  classifiers = [
@@ -484,6 +484,13 @@ class Store:
484
484
  def prepare_readers(self) -> None: ...
485
485
  def complete_cozytensors_manifests(self) -> list[str]: ...
486
486
  def contains(self, hex: str) -> bool: ...
487
+ def update_model_assets(
488
+ self,
489
+ manifest_id: str,
490
+ manifest_length: int,
491
+ copies: Sequence[tuple[str, str, str]] = ...,
492
+ drops: Sequence[str] = ...,
493
+ ) -> dict[str, str | int]: ...
487
494
  def objects(self) -> list[str]: ...
488
495
  def reap_holds(self) -> list[str]: ...
489
496