tensorfs 0.3.8__tar.gz → 0.3.10__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.8 → tensorfs-0.3.10}/Cargo.lock +2 -2
- {tensorfs-0.3.8 → tensorfs-0.3.10}/Cargo.toml +1 -1
- {tensorfs-0.3.8 → tensorfs-0.3.10}/PKG-INFO +1 -1
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/derived.rs +289 -20
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/durability.rs +54 -7
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/gc.rs +26 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/source.rs +60 -15
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/meta.rs +1 -14
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/storage.rs +1 -1
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-py/src/store.rs +24 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/pyproject.toml +1 -1
- {tensorfs-0.3.8 → tensorfs-0.3.10}/python/tensorfs/_ext.pyi +4 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/transport/tests.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/python/tensorfs/__init__.py +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.8 → tensorfs-0.3.10}/python/tensorfs/py.typed +0 -0
|
@@ -392,7 +392,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
392
392
|
|
|
393
393
|
[[package]]
|
|
394
394
|
name = "tensorfs-core"
|
|
395
|
-
version = "0.3.
|
|
395
|
+
version = "0.3.10"
|
|
396
396
|
dependencies = [
|
|
397
397
|
"fs2",
|
|
398
398
|
"minicbor",
|
|
@@ -404,7 +404,7 @@ dependencies = [
|
|
|
404
404
|
|
|
405
405
|
[[package]]
|
|
406
406
|
name = "tensorfs-py"
|
|
407
|
-
version = "0.3.
|
|
407
|
+
version = "0.3.10"
|
|
408
408
|
dependencies = [
|
|
409
409
|
"pyo3",
|
|
410
410
|
"tensorfs-core",
|
|
@@ -143,6 +143,20 @@ pub struct Declaration {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
impl Declaration {
|
|
146
|
+
/// The native declaration encoder shared by Runtime intents and writers.
|
|
147
|
+
pub fn canonical_bytes(&mut self) -> Result<Vec<u8>> {
|
|
148
|
+
self.normalize_and_validate()?;
|
|
149
|
+
self.require_order()?;
|
|
150
|
+
let bytes = crate::canon::write(&self.to_value());
|
|
151
|
+
if bytes.len() > limits::DOC_MAX_BYTES / 2 {
|
|
152
|
+
return refuse(
|
|
153
|
+
Code::SIZE_CAP,
|
|
154
|
+
"derived declaration is over its metadata budget",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
Ok(bytes)
|
|
158
|
+
}
|
|
159
|
+
|
|
146
160
|
fn require_order(&self) -> Result<()> {
|
|
147
161
|
if self.order.is_empty() {
|
|
148
162
|
return refuse(
|
|
@@ -1324,22 +1338,28 @@ fn require_payload_record(store: &Store, object: &ObjectRef, what: &str) -> Resu
|
|
|
1324
1338
|
}
|
|
1325
1339
|
}
|
|
1326
1340
|
|
|
1327
|
-
fn
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
let mut out = Vec::new();
|
|
1341
|
+
fn require_source_roots(store: &Store, declaration: &Declaration) -> Result<()> {
|
|
1342
|
+
if declaration.sources.is_empty() {
|
|
1343
|
+
return Ok(());
|
|
1344
|
+
}
|
|
1345
|
+
let retained = crate::gc::retained_manifests(store)?;
|
|
1333
1346
|
for declared in &declaration.sources {
|
|
1334
|
-
if !
|
|
1347
|
+
if !retained.contains(&declared.manifest) {
|
|
1335
1348
|
return refuse(
|
|
1336
1349
|
Code::ROOT_ABSENT,
|
|
1337
1350
|
format!(
|
|
1338
|
-
"source {}
|
|
1351
|
+
"source {} has no exact retained filesystem root",
|
|
1339
1352
|
declared.manifest.id()
|
|
1340
1353
|
),
|
|
1341
1354
|
);
|
|
1342
1355
|
}
|
|
1356
|
+
}
|
|
1357
|
+
Ok(())
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
fn load_sources(store: &Store, declaration: &Declaration) -> Result<Vec<SourceData>> {
|
|
1361
|
+
let mut out = Vec::new();
|
|
1362
|
+
for declared in &declaration.sources {
|
|
1343
1363
|
let manifest = checkpoint::load_manifest(store, &declared.manifest)?;
|
|
1344
1364
|
if manifest.manifest_id() != declared.manifest.id() {
|
|
1345
1365
|
return refuse(
|
|
@@ -1777,6 +1797,18 @@ pub fn inspect_source(
|
|
|
1777
1797
|
manifest: ObjectRef,
|
|
1778
1798
|
component_names: Vec<String>,
|
|
1779
1799
|
config_names: Vec<String>,
|
|
1800
|
+
) -> Result<SourceInspection> {
|
|
1801
|
+
let hold = meta.acquire_hold("derived-source-inspect")?;
|
|
1802
|
+
let result = inspect_retained_source(store, manifest, component_names, config_names);
|
|
1803
|
+
let released = hold.release(meta);
|
|
1804
|
+
result.and_then(|inspection| released.map(|()| inspection))
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
fn inspect_retained_source(
|
|
1808
|
+
store: &Store,
|
|
1809
|
+
manifest: ObjectRef,
|
|
1810
|
+
component_names: Vec<String>,
|
|
1811
|
+
config_names: Vec<String>,
|
|
1780
1812
|
) -> Result<SourceInspection> {
|
|
1781
1813
|
let alias = "source".to_string();
|
|
1782
1814
|
let mut declaration = Declaration {
|
|
@@ -1806,7 +1838,8 @@ pub fn inspect_source(
|
|
|
1806
1838
|
max_new_bytes: 0,
|
|
1807
1839
|
};
|
|
1808
1840
|
declaration.normalize_and_validate()?;
|
|
1809
|
-
|
|
1841
|
+
require_source_roots(store, &declaration)?;
|
|
1842
|
+
let mut sources = load_sources(store, &declaration)?;
|
|
1810
1843
|
let source = sources.pop().ok_or_else(|| Refusal {
|
|
1811
1844
|
code: Code::ROOT_ABSENT,
|
|
1812
1845
|
detail: "source inspection resolved no source".into(),
|
|
@@ -1862,20 +1895,15 @@ pub fn begin(
|
|
|
1862
1895
|
"WriterSessionId must be positive and inside the canonical integer range",
|
|
1863
1896
|
);
|
|
1864
1897
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
let declaration_text = declaration.canonical_text();
|
|
1868
|
-
if declaration_text.len() > limits::DOC_MAX_BYTES / 2 {
|
|
1869
|
-
return refuse(
|
|
1870
|
-
Code::SIZE_CAP,
|
|
1871
|
-
"derived declaration is over its metadata budget",
|
|
1872
|
-
);
|
|
1873
|
-
}
|
|
1898
|
+
let declaration_text =
|
|
1899
|
+
String::from_utf8(declaration.canonical_bytes()?).expect("canonical JSON is UTF-8");
|
|
1874
1900
|
|
|
1875
1901
|
// The writer hold is registered before source metadata is touched. It is the live
|
|
1876
1902
|
// process proof that keeps exclusive GC out even for a source-free create.
|
|
1877
1903
|
let writer_hold = meta.acquire_hold("derived-writer")?;
|
|
1878
|
-
let sources = match
|
|
1904
|
+
let sources = match require_source_roots(store, &declaration)
|
|
1905
|
+
.and_then(|()| load_sources(store, &declaration))
|
|
1906
|
+
{
|
|
1879
1907
|
Ok(sources) => sources,
|
|
1880
1908
|
Err(error) => {
|
|
1881
1909
|
release_guards(meta, Some(writer_hold), Vec::new());
|
|
@@ -2323,6 +2351,9 @@ fn receipt_from(_store: &Store, row: &TransactionRow) -> Result<ReceiptFacts> {
|
|
|
2323
2351
|
})
|
|
2324
2352
|
}
|
|
2325
2353
|
|
|
2354
|
+
/// The caller retains Begin's writer hold and source leases through this call.
|
|
2355
|
+
/// Root membership was admitted at begin; the live hold now protects those exact
|
|
2356
|
+
/// bytes even if their original repository/session/private root is released.
|
|
2326
2357
|
pub fn commit(
|
|
2327
2358
|
store: &Store,
|
|
2328
2359
|
meta: &Meta,
|
|
@@ -2335,7 +2366,7 @@ pub fn commit(
|
|
|
2335
2366
|
open_row(&mut txn.rows, &transaction, writer_session)?.clone()
|
|
2336
2367
|
};
|
|
2337
2368
|
let declaration = declaration(&row_before)?;
|
|
2338
|
-
let sources =
|
|
2369
|
+
let sources = load_sources(store, &declaration)?;
|
|
2339
2370
|
let (header, closure, registry_adoptions) = build_header(
|
|
2340
2371
|
&declaration,
|
|
2341
2372
|
&sources,
|
|
@@ -2693,6 +2724,235 @@ mod tests {
|
|
|
2693
2724
|
}
|
|
2694
2725
|
}
|
|
2695
2726
|
|
|
2727
|
+
fn inherited_declaration(manifest: ObjectRef) -> Declaration {
|
|
2728
|
+
Declaration {
|
|
2729
|
+
sources: vec![Source {
|
|
2730
|
+
alias: "source".into(),
|
|
2731
|
+
manifest,
|
|
2732
|
+
}],
|
|
2733
|
+
components: vec![ComponentDeclaration {
|
|
2734
|
+
target: "inherited".into(),
|
|
2735
|
+
source: Some("source".into()),
|
|
2736
|
+
source_component: Some("model".into()),
|
|
2737
|
+
drop: Vec::new(),
|
|
2738
|
+
add: Vec::new(),
|
|
2739
|
+
}],
|
|
2740
|
+
configs: Vec::new(),
|
|
2741
|
+
order: vec![("inherited".into(), "weight".into())],
|
|
2742
|
+
max_new_bytes: 0,
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
#[test]
|
|
2747
|
+
fn retained_source_admission_and_writer_hold_share_the_gc_roots() {
|
|
2748
|
+
let root = std::env::temp_dir().join(format!(
|
|
2749
|
+
"tensorfs-retained-source-{}",
|
|
2750
|
+
crate::meta::now_nanos_unique()
|
|
2751
|
+
));
|
|
2752
|
+
let store = Store::init(&root).unwrap();
|
|
2753
|
+
let meta = Meta::open(&store).unwrap();
|
|
2754
|
+
let source_id = format!("sha256:{}", "81".repeat(32));
|
|
2755
|
+
let source_writer = begin(&store, &meta, &source_id, 1, created_declaration()).unwrap();
|
|
2756
|
+
let bytes = vec![0x21; 2048];
|
|
2757
|
+
add_part(
|
|
2758
|
+
&store,
|
|
2759
|
+
&meta,
|
|
2760
|
+
&source_id,
|
|
2761
|
+
1,
|
|
2762
|
+
("model", "weight", "value"),
|
|
2763
|
+
&mut bytes.as_slice(),
|
|
2764
|
+
)
|
|
2765
|
+
.unwrap();
|
|
2766
|
+
let source = commit(&store, &meta, &source_id, 1).unwrap();
|
|
2767
|
+
release_guards(
|
|
2768
|
+
&meta,
|
|
2769
|
+
Some(source_writer.writer_hold),
|
|
2770
|
+
source_writer.source_leases,
|
|
2771
|
+
);
|
|
2772
|
+
let preview = || {
|
|
2773
|
+
inspect_source(
|
|
2774
|
+
&store,
|
|
2775
|
+
&meta,
|
|
2776
|
+
source.manifest.clone(),
|
|
2777
|
+
vec!["model".into()],
|
|
2778
|
+
Vec::new(),
|
|
2779
|
+
)
|
|
2780
|
+
};
|
|
2781
|
+
|
|
2782
|
+
// Committed pending output and owner-adopted output have real filesystem roots.
|
|
2783
|
+
preview().unwrap();
|
|
2784
|
+
adopt(&store, &meta, &source_id, "source-private").unwrap();
|
|
2785
|
+
preview().unwrap();
|
|
2786
|
+
dispose(&store, &meta, &source_id).unwrap();
|
|
2787
|
+
assert_eq!(preview().unwrap_err().code, Code::ROOT_ABSENT);
|
|
2788
|
+
let target_id = format!("sha256:{}", "82".repeat(32));
|
|
2789
|
+
assert_eq!(
|
|
2790
|
+
begin(
|
|
2791
|
+
&store,
|
|
2792
|
+
&meta,
|
|
2793
|
+
&target_id,
|
|
2794
|
+
1,
|
|
2795
|
+
inherited_declaration(source.manifest.clone())
|
|
2796
|
+
)
|
|
2797
|
+
.err()
|
|
2798
|
+
.unwrap()
|
|
2799
|
+
.code,
|
|
2800
|
+
Code::ROOT_ABSENT
|
|
2801
|
+
);
|
|
2802
|
+
assert!(matches!(
|
|
2803
|
+
lookup(&store, &meta, &target_id).unwrap(),
|
|
2804
|
+
Lookup::Absent
|
|
2805
|
+
));
|
|
2806
|
+
|
|
2807
|
+
// The coordinator's actual prepared-source lifecycle names a private candidate.
|
|
2808
|
+
let operation = "prepared-source";
|
|
2809
|
+
let request_digest = format!("sha256:{}", "83".repeat(32));
|
|
2810
|
+
let catalog = crate::catalog::Catalog::open(&root).unwrap();
|
|
2811
|
+
let crate::catalog::SourcePreparation::Open(source_guard) = catalog
|
|
2812
|
+
.begin_source_preparation(operation, &request_digest)
|
|
2813
|
+
.unwrap()
|
|
2814
|
+
else {
|
|
2815
|
+
panic!("source operation already prepared")
|
|
2816
|
+
};
|
|
2817
|
+
crate::ingest::transaction::ensure_session_root(&root, operation, "_tensorfs").unwrap();
|
|
2818
|
+
crate::ingest::transaction::name_candidates(
|
|
2819
|
+
&root,
|
|
2820
|
+
operation,
|
|
2821
|
+
std::slice::from_ref(&source.manifest),
|
|
2822
|
+
)
|
|
2823
|
+
.unwrap();
|
|
2824
|
+
// An actual candidate is retained even before the rebuildable result row is written.
|
|
2825
|
+
preview().unwrap();
|
|
2826
|
+
catalog
|
|
2827
|
+
.commit_source_preparation(
|
|
2828
|
+
operation,
|
|
2829
|
+
&request_digest,
|
|
2830
|
+
&[crate::catalog::SourcePreparationResult {
|
|
2831
|
+
slot: "source".into(),
|
|
2832
|
+
profile: "proof".into(),
|
|
2833
|
+
manifest_sha256: source.manifest.sha256.clone(),
|
|
2834
|
+
manifest_length: source.manifest.length,
|
|
2835
|
+
}],
|
|
2836
|
+
)
|
|
2837
|
+
.unwrap();
|
|
2838
|
+
drop(source_guard);
|
|
2839
|
+
crate::gc::collect(&root, false).unwrap();
|
|
2840
|
+
preview().unwrap();
|
|
2841
|
+
let mut wrong = inherited_declaration(source.manifest.clone());
|
|
2842
|
+
wrong.sources[0].manifest.length += 1;
|
|
2843
|
+
assert_eq!(
|
|
2844
|
+
begin(&store, &meta, &target_id, 1, wrong)
|
|
2845
|
+
.err()
|
|
2846
|
+
.unwrap()
|
|
2847
|
+
.code,
|
|
2848
|
+
Code::ROOT_ABSENT
|
|
2849
|
+
);
|
|
2850
|
+
|
|
2851
|
+
let writer = begin(
|
|
2852
|
+
&store,
|
|
2853
|
+
&meta,
|
|
2854
|
+
&target_id,
|
|
2855
|
+
1,
|
|
2856
|
+
inherited_declaration(source.manifest.clone()),
|
|
2857
|
+
)
|
|
2858
|
+
.unwrap();
|
|
2859
|
+
crate::ingest::source::release_model_source(&store, operation).unwrap();
|
|
2860
|
+
assert_eq!(preview().unwrap_err().code, Code::ROOT_ABSENT);
|
|
2861
|
+
writer.writer_hold.still_registered(&meta).unwrap();
|
|
2862
|
+
assert_eq!(
|
|
2863
|
+
crate::gc::collect(&root, false).unwrap_err().code,
|
|
2864
|
+
Code::STORE_BUSY
|
|
2865
|
+
);
|
|
2866
|
+
let inherited = commit(&store, &meta, &target_id, 1).unwrap();
|
|
2867
|
+
assert_eq!(inherited.inherited_payload_bytes, 2048);
|
|
2868
|
+
assert!(inherited.added_objects.is_empty());
|
|
2869
|
+
release_guards(&meta, Some(writer.writer_hold), writer.source_leases);
|
|
2870
|
+
crate::gc::collect(&root, false).unwrap();
|
|
2871
|
+
store.read_manifest(&inherited.manifest).unwrap();
|
|
2872
|
+
inspect_source(
|
|
2873
|
+
&store,
|
|
2874
|
+
&meta,
|
|
2875
|
+
inherited.manifest.clone(),
|
|
2876
|
+
vec!["inherited".into()],
|
|
2877
|
+
Vec::new(),
|
|
2878
|
+
)
|
|
2879
|
+
.unwrap();
|
|
2880
|
+
dispose(&store, &meta, &target_id).unwrap();
|
|
2881
|
+
assert_eq!(
|
|
2882
|
+
inspect_source(
|
|
2883
|
+
&store,
|
|
2884
|
+
&meta,
|
|
2885
|
+
inherited.manifest,
|
|
2886
|
+
vec!["inherited".into()],
|
|
2887
|
+
Vec::new()
|
|
2888
|
+
)
|
|
2889
|
+
.unwrap_err()
|
|
2890
|
+
.code,
|
|
2891
|
+
Code::ROOT_ABSENT
|
|
2892
|
+
);
|
|
2893
|
+
let _ = std::fs::remove_dir_all(root);
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
#[test]
|
|
2897
|
+
fn retained_source_still_refuses_corrupt_payload_and_abandoned_writer() {
|
|
2898
|
+
let root = std::env::temp_dir().join(format!(
|
|
2899
|
+
"tensorfs-retained-source-refusal-{}",
|
|
2900
|
+
crate::meta::now_nanos_unique()
|
|
2901
|
+
));
|
|
2902
|
+
let store = Store::init(&root).unwrap();
|
|
2903
|
+
let meta = Meta::open(&store).unwrap();
|
|
2904
|
+
let source_id = format!("sha256:{}", "84".repeat(32));
|
|
2905
|
+
let source_writer = begin(&store, &meta, &source_id, 1, created_declaration()).unwrap();
|
|
2906
|
+
let bytes = vec![0x31; 2048];
|
|
2907
|
+
let part = add_part(
|
|
2908
|
+
&store,
|
|
2909
|
+
&meta,
|
|
2910
|
+
&source_id,
|
|
2911
|
+
1,
|
|
2912
|
+
("model", "weight", "value"),
|
|
2913
|
+
&mut bytes.as_slice(),
|
|
2914
|
+
)
|
|
2915
|
+
.unwrap();
|
|
2916
|
+
let source = commit(&store, &meta, &source_id, 1).unwrap();
|
|
2917
|
+
release_guards(
|
|
2918
|
+
&meta,
|
|
2919
|
+
Some(source_writer.writer_hold),
|
|
2920
|
+
source_writer.source_leases,
|
|
2921
|
+
);
|
|
2922
|
+
let target_id = format!("sha256:{}", "85".repeat(32));
|
|
2923
|
+
let writer = begin(
|
|
2924
|
+
&store,
|
|
2925
|
+
&meta,
|
|
2926
|
+
&target_id,
|
|
2927
|
+
1,
|
|
2928
|
+
inherited_declaration(source.manifest.clone()),
|
|
2929
|
+
)
|
|
2930
|
+
.unwrap();
|
|
2931
|
+
assert!(fence(&meta, &target_id, 1).unwrap());
|
|
2932
|
+
abandon(&store, &meta, &target_id).unwrap();
|
|
2933
|
+
assert_eq!(
|
|
2934
|
+
commit(&store, &meta, &target_id, 1).unwrap_err().code,
|
|
2935
|
+
Code::TRANSACTION_CLOSED
|
|
2936
|
+
);
|
|
2937
|
+
release_guards(&meta, Some(writer.writer_hold), writer.source_leases);
|
|
2938
|
+
let path = store.blob_path(&part.part.segments()[0].sha256);
|
|
2939
|
+
std::fs::remove_file(&path).unwrap();
|
|
2940
|
+
std::fs::write(path, vec![0x32; 2048]).unwrap();
|
|
2941
|
+
assert_eq!(
|
|
2942
|
+
inspect_source(
|
|
2943
|
+
&store,
|
|
2944
|
+
&meta,
|
|
2945
|
+
source.manifest,
|
|
2946
|
+
vec!["model".into()],
|
|
2947
|
+
Vec::new()
|
|
2948
|
+
)
|
|
2949
|
+
.unwrap_err()
|
|
2950
|
+
.code,
|
|
2951
|
+
Code::OBJECT_CORRUPT
|
|
2952
|
+
);
|
|
2953
|
+
let _ = std::fs::remove_dir_all(root);
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2696
2956
|
#[test]
|
|
2697
2957
|
fn private_adoption_retains_real_payload_until_dispose_without_a_repository() {
|
|
2698
2958
|
let root = std::env::temp_dir().join(format!(
|
|
@@ -2939,6 +3199,15 @@ mod tests {
|
|
|
2939
3199
|
)
|
|
2940
3200
|
.unwrap()
|
|
2941
3201
|
.unwrap();
|
|
3202
|
+
// An owner-retained checkpoint is a GC root before any public release exists.
|
|
3203
|
+
inspect_source(
|
|
3204
|
+
&store,
|
|
3205
|
+
&meta,
|
|
3206
|
+
manifest_ref.clone(),
|
|
3207
|
+
vec!["model".into()],
|
|
3208
|
+
Vec::new(),
|
|
3209
|
+
)
|
|
3210
|
+
.unwrap();
|
|
2942
3211
|
store
|
|
2943
3212
|
.apply_repository(
|
|
2944
3213
|
Some(&checkpointed.canonical_bytes()),
|
|
@@ -185,21 +185,34 @@ pub fn canonical_refs(mut refs: Vec<ObjectRef>) -> Vec<ObjectRef> {
|
|
|
185
185
|
/// Read an admitted link from the local Store. Unlike `chain`, this does not require an
|
|
186
186
|
/// NFS binding: the ordinary transfer plane may have obtained it from the hub.
|
|
187
187
|
pub fn local_link(store: &Store, object: &ObjectRef) -> Result<Link> {
|
|
188
|
-
|
|
188
|
+
Link::parse(&read_document(store, object, Link::MAX_BYTES)?)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Bound the actual pinned file before allocation, not only the untrusted claimed length.
|
|
192
|
+
/// Source and derived progress use the same exact ObjectRef admission contract as links.
|
|
193
|
+
pub(crate) fn read_document(store: &Store, object: &ObjectRef, cap: usize) -> Result<Vec<u8>> {
|
|
194
|
+
if object.length > cap as u64 {
|
|
195
|
+
return refuse(Code::COUNT_CAP, "checkpoint document exceeds its byte cap");
|
|
196
|
+
}
|
|
197
|
+
let mut file = store.open_verified(&object.sha256)?;
|
|
198
|
+
if file.len() != object.length {
|
|
189
199
|
return refuse(
|
|
190
|
-
Code::
|
|
191
|
-
"checkpoint
|
|
200
|
+
Code::LENGTH_MISMATCH,
|
|
201
|
+
"checkpoint document differs from its ObjectRef length",
|
|
192
202
|
);
|
|
193
203
|
}
|
|
194
|
-
let mut bytes =
|
|
195
|
-
|
|
204
|
+
let mut bytes = vec![0; object.length as usize];
|
|
205
|
+
std::io::Read::read_exact(&mut file, &mut bytes).map_err(|error| crate::err::Refusal {
|
|
206
|
+
code: Code::IO_FAILED,
|
|
207
|
+
detail: format!("read checkpoint document: {error}"),
|
|
208
|
+
})?;
|
|
196
209
|
if ObjectRef::of(&bytes) != *object {
|
|
197
210
|
return refuse(
|
|
198
211
|
Code::OBJECT_ID_MISMATCH,
|
|
199
|
-
"checkpoint
|
|
212
|
+
"checkpoint document differs from its ObjectRef",
|
|
200
213
|
);
|
|
201
214
|
}
|
|
202
|
-
|
|
215
|
+
Ok(bytes)
|
|
203
216
|
}
|
|
204
217
|
|
|
205
218
|
/// Each call reads one link and returns a bounded window, so a caller can fetch links and
|
|
@@ -1030,6 +1043,40 @@ pub fn restore_conversion(
|
|
|
1030
1043
|
mod tests {
|
|
1031
1044
|
use super::*;
|
|
1032
1045
|
|
|
1046
|
+
#[test]
|
|
1047
|
+
fn checkpoint_documents_refuse_wrong_lengths_before_reading_into_memory() {
|
|
1048
|
+
let root = std::env::temp_dir().join(format!(
|
|
1049
|
+
"tensorfs-document-bound-{}",
|
|
1050
|
+
crate::meta::now_nanos_unique()
|
|
1051
|
+
));
|
|
1052
|
+
let store = Store::init(&root).unwrap();
|
|
1053
|
+
let bytes = b"a verified resident object";
|
|
1054
|
+
let object = store
|
|
1055
|
+
.put_stream(
|
|
1056
|
+
&mut bytes.as_slice(),
|
|
1057
|
+
Some(&ObjectRef::of(bytes)),
|
|
1058
|
+
&Default::default(),
|
|
1059
|
+
)
|
|
1060
|
+
.unwrap()
|
|
1061
|
+
.obj;
|
|
1062
|
+
let mut short = object.clone();
|
|
1063
|
+
short.length = 1;
|
|
1064
|
+
assert_eq!(
|
|
1065
|
+
local_link(&store, &short).unwrap_err().code,
|
|
1066
|
+
Code::LENGTH_MISMATCH
|
|
1067
|
+
);
|
|
1068
|
+
assert_eq!(
|
|
1069
|
+
read_document(&store, &short, 1).unwrap_err().code,
|
|
1070
|
+
Code::LENGTH_MISMATCH
|
|
1071
|
+
);
|
|
1072
|
+
assert_eq!(
|
|
1073
|
+
read_document(&store, &object, 1).unwrap_err().code,
|
|
1074
|
+
Code::COUNT_CAP
|
|
1075
|
+
);
|
|
1076
|
+
assert_eq!(read_document(&store, &object, bytes.len()).unwrap(), bytes);
|
|
1077
|
+
std::fs::remove_dir_all(root).unwrap();
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1033
1080
|
fn object(byte: &str, length: u64) -> ObjectRef {
|
|
1034
1081
|
ObjectRef {
|
|
1035
1082
|
sha256: byte.repeat(32),
|
|
@@ -154,6 +154,32 @@ pub fn session_holds(store: &Store) -> Result<(Vec<HeldKey>, Vec<String>)> {
|
|
|
154
154
|
Ok((holds, sessions))
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
/// The same exact manifest roots used by reclamation, without a payload census.
|
|
158
|
+
/// Membership is retention, not caller authorization. A consumer must acquire its
|
|
159
|
+
/// live hold before checking roots so GC cannot remove bytes during admission.
|
|
160
|
+
pub(crate) fn retained_manifests(store: &Store) -> Result<Vec<crate::ids::ObjectRef>> {
|
|
161
|
+
let mut roots = Vec::new();
|
|
162
|
+
for (repository, _) in storage::read_repositories(store.root())? {
|
|
163
|
+
roots.extend(
|
|
164
|
+
repository
|
|
165
|
+
.checkpoints
|
|
166
|
+
.into_iter()
|
|
167
|
+
.map(|checkpoint| checkpoint.manifest),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
let (holds, _) = session_holds(store)?;
|
|
171
|
+
roots.extend(
|
|
172
|
+
holds
|
|
173
|
+
.into_iter()
|
|
174
|
+
.filter(|hold| hold.kind == "manifest")
|
|
175
|
+
.map(|hold| crate::ids::ObjectRef {
|
|
176
|
+
sha256: hold.sha256,
|
|
177
|
+
length: hold.length,
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
Ok(roots)
|
|
181
|
+
}
|
|
182
|
+
|
|
157
183
|
/// One complete pass: exclusivity, orphan temps, census, plan, delete, catalog repair.
|
|
158
184
|
pub fn collect(root: &Path, dry_run: bool) -> Result<Report> {
|
|
159
185
|
let store = Store::open(root)?;
|
|
@@ -1207,11 +1207,51 @@ fn prepare_model_source_with_registry(
|
|
|
1207
1207
|
for source in &sources {
|
|
1208
1208
|
validate_prepared_result(store, source)?;
|
|
1209
1209
|
}
|
|
1210
|
+
// The final response can be lost after its catalog commit. Its existing local
|
|
1211
|
+
// cursors must remain discoverable without converting or reading source bodies.
|
|
1212
|
+
let mut checkpoints = Vec::new();
|
|
1213
|
+
let dir = transaction::candidate_dir(store.root(), &request.operation_id);
|
|
1214
|
+
for (requested, (plan_hex, _)) in request.profiles.iter().zip(&plans) {
|
|
1215
|
+
let plan_digest = format!("sha256:{plan_hex}");
|
|
1216
|
+
let chain = format!("{}/{}", request.operation_id, requested.slot);
|
|
1217
|
+
let journal = super::journal::Journal::open(&dir, plan_hex)?;
|
|
1218
|
+
let local = journal.local_head()?.ok_or_else(|| Refusal {
|
|
1219
|
+
code: Code::DURABILITY_UNPROVEN,
|
|
1220
|
+
detail: "completed source has no local checkpoint cursor".into(),
|
|
1221
|
+
})?;
|
|
1222
|
+
let supplied = request
|
|
1223
|
+
.checkpoints
|
|
1224
|
+
.iter()
|
|
1225
|
+
.find(|(slot, _)| slot == &requested.slot)
|
|
1226
|
+
.map(|(_, head)| head);
|
|
1227
|
+
let head = durability::checkpoint_predecessor(
|
|
1228
|
+
store,
|
|
1229
|
+
&durability::Policy {
|
|
1230
|
+
chain: &chain,
|
|
1231
|
+
operation: &request.operation_id,
|
|
1232
|
+
plan: &plan_digest,
|
|
1233
|
+
interval: durability::INTERVAL_BYTES,
|
|
1234
|
+
},
|
|
1235
|
+
Some(&local),
|
|
1236
|
+
supplied,
|
|
1237
|
+
)?
|
|
1238
|
+
.expect("a local cursor was supplied");
|
|
1239
|
+
let held = durability::local_chain(store, &head, &plan_digest)?;
|
|
1240
|
+
checkpoints.push(SourceCheckpoint {
|
|
1241
|
+
slot: requested.slot.clone(),
|
|
1242
|
+
plan_digest,
|
|
1243
|
+
head: durability::Head {
|
|
1244
|
+
head: Some(head),
|
|
1245
|
+
links: held.links,
|
|
1246
|
+
bytes: held.bytes,
|
|
1247
|
+
},
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1210
1250
|
return Ok(PreparedModelSources {
|
|
1211
1251
|
replayed: true,
|
|
1212
1252
|
complete: true,
|
|
1213
1253
|
sources,
|
|
1214
|
-
checkpoints
|
|
1254
|
+
checkpoints,
|
|
1215
1255
|
spent: request
|
|
1216
1256
|
.roster
|
|
1217
1257
|
.iter()
|
|
@@ -1257,20 +1297,8 @@ fn prepare_model_source_with_registry(
|
|
|
1257
1297
|
durability::retain_local_chain(store, head, &request.operation_id)?;
|
|
1258
1298
|
if journal.is_empty() {
|
|
1259
1299
|
if let Some(progress) = held.progress.last() {
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
Code::COUNT_CAP,
|
|
1263
|
-
"checkpoint progress exceeds the document byte cap",
|
|
1264
|
-
);
|
|
1265
|
-
}
|
|
1266
|
-
let mut bytes = Vec::with_capacity(progress.length as usize);
|
|
1267
|
-
store.read_into(&progress.sha256, &mut bytes)?;
|
|
1268
|
-
if ObjectRef::of(&bytes) != *progress {
|
|
1269
|
-
return refuse(
|
|
1270
|
-
Code::OBJECT_ID_MISMATCH,
|
|
1271
|
-
"checkpoint progress differs from its ObjectRef",
|
|
1272
|
-
);
|
|
1273
|
-
}
|
|
1300
|
+
let bytes =
|
|
1301
|
+
durability::read_document(store, progress, crate::limits::DOC_MAX_BYTES)?;
|
|
1274
1302
|
drop(journal);
|
|
1275
1303
|
super::journal::install(&dir, &plan_hex, &bytes)?;
|
|
1276
1304
|
journal = super::journal::Journal::open(&dir, &plan_hex)?;
|
|
@@ -1583,6 +1611,19 @@ mod tests {
|
|
|
1583
1611
|
.unwrap();
|
|
1584
1612
|
assert!(replay.replayed);
|
|
1585
1613
|
assert_eq!(replay.sources, first.sources);
|
|
1614
|
+
assert_eq!(replay.checkpoints, first.checkpoints);
|
|
1615
|
+
let mut bodies_gone = request.clone();
|
|
1616
|
+
bodies_gone.landed.clear();
|
|
1617
|
+
for row in &bodies_gone.roster {
|
|
1618
|
+
fs::remove_file(&row.path).unwrap();
|
|
1619
|
+
}
|
|
1620
|
+
let recovered_heads =
|
|
1621
|
+
prepare_model_source_with_registry(&store, &bodies_gone, "fixture:registry", ®istry)
|
|
1622
|
+
.unwrap();
|
|
1623
|
+
assert_eq!(recovered_heads.checkpoints, first.checkpoints);
|
|
1624
|
+
// Remaining arms need their original carriers again.
|
|
1625
|
+
carrier(&request.roster[0].path, "weight", [1, 2, 3, 4]);
|
|
1626
|
+
carrier(&request.roster[1].path, "weight", [5, 6, 7, 8]);
|
|
1586
1627
|
|
|
1587
1628
|
let mut changed = request.clone();
|
|
1588
1629
|
changed.roster[0].object_id = format!("sha256:{}", "33".repeat(32));
|
|
@@ -2239,6 +2280,10 @@ mod tests {
|
|
|
2239
2280
|
)
|
|
2240
2281
|
.unwrap();
|
|
2241
2282
|
assert!(replay.replayed && replay.complete);
|
|
2283
|
+
assert_eq!(
|
|
2284
|
+
replay.checkpoints, resumed.checkpoints,
|
|
2285
|
+
"a lost final response replays even with an old ACK"
|
|
2286
|
+
);
|
|
2242
2287
|
let mut unrelated = registry.clone();
|
|
2243
2288
|
unrelated.entries[0]
|
|
2244
2289
|
.provenance
|
|
@@ -11,7 +11,7 @@ use fs2::FileExt;
|
|
|
11
11
|
use rusqlite::{params, Connection};
|
|
12
12
|
|
|
13
13
|
use crate::err::{refuse, Code, Refusal, Result};
|
|
14
|
-
use crate::ids::
|
|
14
|
+
use crate::ids::ascii_name;
|
|
15
15
|
use crate::limits;
|
|
16
16
|
use crate::store::Store;
|
|
17
17
|
|
|
@@ -82,19 +82,6 @@ impl Meta {
|
|
|
82
82
|
self.lease_dir.join(format!("{id}.lease"))
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
pub fn manifest_is_released(&self, digest: &str) -> Result<bool> {
|
|
86
|
-
hex64("manifest", digest)?;
|
|
87
|
-
let count: u64 = self
|
|
88
|
-
.connection()?
|
|
89
|
-
.query_row(
|
|
90
|
-
"SELECT count(*) FROM tensorfs_released_manifests WHERE manifest_sha256=?1",
|
|
91
|
-
[digest],
|
|
92
|
-
|row| row.get(0),
|
|
93
|
-
)
|
|
94
|
-
.map_err(|error| sql("query released manifest", error))?;
|
|
95
|
-
Ok(count != 0)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
85
|
pub(crate) fn txn(&self) -> Result<Txn> {
|
|
99
86
|
let connection = self.connection()?;
|
|
100
87
|
connection
|
|
@@ -1223,7 +1223,7 @@ impl GcDelete {
|
|
|
1223
1223
|
}
|
|
1224
1224
|
}
|
|
1225
1225
|
|
|
1226
|
-
fn read_repositories(root: &Path) -> Result<Vec<(Repository, Vec<u8>)>> {
|
|
1226
|
+
pub(crate) fn read_repositories(root: &Path) -> Result<Vec<(Repository, Vec<u8>)>> {
|
|
1227
1227
|
let mut result = Vec::new();
|
|
1228
1228
|
for org_path in read_dir(&root.join("repos"))? {
|
|
1229
1229
|
if !org_path.is_dir() {
|
|
@@ -1437,6 +1437,24 @@ impl PyStore {
|
|
|
1437
1437
|
Ok(out)
|
|
1438
1438
|
}
|
|
1439
1439
|
|
|
1440
|
+
/// Canonical TensorFS declaration bytes for the current producer intent.
|
|
1441
|
+
#[allow(clippy::too_many_arguments)]
|
|
1442
|
+
#[pyo3(signature = (sources, targets, configs, order, max_new_bytes))]
|
|
1443
|
+
fn derived_declaration<'p>(
|
|
1444
|
+
&self,
|
|
1445
|
+
py: Python<'p>,
|
|
1446
|
+
sources: &Bound<'_, PyDict>,
|
|
1447
|
+
targets: &Bound<'_, PyDict>,
|
|
1448
|
+
configs: &Bound<'_, PyDict>,
|
|
1449
|
+
order: Vec<(String, String)>,
|
|
1450
|
+
max_new_bytes: u64,
|
|
1451
|
+
) -> PyResult<Bound<'p, PyBytes>> {
|
|
1452
|
+
let mut declaration =
|
|
1453
|
+
parse_declaration(py, sources, targets, configs, order, max_new_bytes)?;
|
|
1454
|
+
let bytes = declaration.canonical_bytes().or_refuse(py)?;
|
|
1455
|
+
Ok(PyBytes::new(py, &bytes))
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1440
1458
|
/// Open one exact tfs-019 transaction. Python supplies typed plain values; canonical
|
|
1441
1459
|
/// declaration bytes are constructed and locked only by the compiled core.
|
|
1442
1460
|
// PyO3 exposes these as six named boundary fields. Packing them into an opaque dict
|
|
@@ -1874,6 +1892,12 @@ impl PyDerivedWriter {
|
|
|
1874
1892
|
fn commit<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyAny>> {
|
|
1875
1893
|
let value = py
|
|
1876
1894
|
.detach(|| {
|
|
1895
|
+
let guards = self.guards.lock().expect("writer hold mutex poisoned");
|
|
1896
|
+
let guards = guards.as_ref().ok_or_else(|| Refusal {
|
|
1897
|
+
code: Code::LEASE_REVOKED,
|
|
1898
|
+
detail: "derived writer holds were released".into(),
|
|
1899
|
+
})?;
|
|
1900
|
+
guards.writer_hold.still_registered(&self.meta)?;
|
|
1877
1901
|
derived::commit(
|
|
1878
1902
|
&self.store,
|
|
1879
1903
|
&self.meta,
|
|
@@ -481,6 +481,10 @@ class Store:
|
|
|
481
481
|
components: Sequence[str],
|
|
482
482
|
configs: Sequence[str],
|
|
483
483
|
) -> dict[str, Any]: ...
|
|
484
|
+
def derived_declaration(
|
|
485
|
+
self, sources: dict[str, Any], targets: dict[str, Any], configs: dict[str, Any],
|
|
486
|
+
order: Sequence[tuple[str, str]], max_new_bytes: int,
|
|
487
|
+
) -> bytes: ...
|
|
484
488
|
def begin_derived(
|
|
485
489
|
self,
|
|
486
490
|
transaction_id: str,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor
RENAMED
|
File without changes
|
{tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json
RENAMED
|
File without changes
|
{tensorfs-0.3.8 → tensorfs-0.3.10}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|