tensorfs 0.3.0__tar.gz → 0.3.1__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 (82) hide show
  1. {tensorfs-0.3.0 → tensorfs-0.3.1}/Cargo.lock +2 -2
  2. {tensorfs-0.3.0 → tensorfs-0.3.1}/Cargo.toml +1 -1
  3. {tensorfs-0.3.0 → tensorfs-0.3.1}/PKG-INFO +1 -1
  4. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/ingest.rs +1 -1
  5. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/main.rs +221 -0
  6. tensorfs-0.3.1/crates/tensorfs-core/src/home.rs +195 -0
  7. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/lib.rs +2 -0
  8. tensorfs-0.3.1/crates/tensorfs-core/src/repo_cache.rs +337 -0
  9. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/store.rs +261 -20
  10. tensorfs-0.3.1/crates/tensorfs-core/tests/home.rs +195 -0
  11. tensorfs-0.3.1/crates/tensorfs-core/tests/repo_cache.rs +373 -0
  12. tensorfs-0.3.1/crates/tensorfs-core/tests/store_boundaries.rs +268 -0
  13. tensorfs-0.3.1/crates/tensorfs-core/tests/version.rs +34 -0
  14. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/src/lib.rs +9 -0
  15. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/src/store.rs +91 -11
  16. {tensorfs-0.3.0 → tensorfs-0.3.1}/pyproject.toml +7 -1
  17. {tensorfs-0.3.0 → tensorfs-0.3.1}/python/tensorfs/__init__.py +94 -9
  18. tensorfs-0.3.1/python/tensorfs/_ext.pyi +41 -0
  19. tensorfs-0.3.1/python/tensorfs/py.typed +0 -0
  20. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/Cargo.toml +0 -0
  21. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/b64.rs +0 -0
  22. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  23. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  24. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  25. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  26. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
  27. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  28. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  29. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/canon.rs +0 -0
  30. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/capability.rs +0 -0
  31. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/catalog.rs +0 -0
  32. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/cbor.rs +0 -0
  33. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  34. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/corpus.rs +0 -0
  35. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/derived.rs +0 -0
  36. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/dtype.rs +0 -0
  37. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/err.rs +0 -0
  38. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/fetch.rs +0 -0
  39. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/fill.rs +0 -0
  40. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/fit.rs +0 -0
  41. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/gc.rs +0 -0
  42. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/header.rs +0 -0
  43. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ids.rs +0 -0
  44. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  45. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
  46. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
  47. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  48. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  49. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/source.rs +0 -0
  50. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  51. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
  52. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/jcs.rs +0 -0
  53. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/limits.rs +0 -0
  54. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/machine.rs +0 -0
  55. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/manifest.rs +0 -0
  56. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/meta.rs +0 -0
  57. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/project.rs +0 -0
  58. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/read.rs +0 -0
  59. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/receipt.rs +0 -0
  60. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/registry.rs +0 -0
  61. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/relation.rs +0 -0
  62. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/repository.rs +0 -0
  63. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/sha256.rs +0 -0
  64. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/spec.rs +0 -0
  65. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/storage.rs +0 -0
  66. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/src/vectors.rs +0 -0
  67. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fit.rs +0 -0
  68. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  69. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  70. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  71. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  72. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  73. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  74. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  75. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/liveness.rs +0 -0
  76. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  77. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  78. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  79. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/Cargo.toml +0 -0
  80. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/src/docs.rs +0 -0
  81. {tensorfs-0.3.0 → tensorfs-0.3.1}/crates/tensorfs-py/src/errors.rs +0 -0
  82. {tensorfs-0.3.0 → tensorfs-0.3.1}/python/tensorfs/errors.py +0 -0
@@ -249,7 +249,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
249
249
 
250
250
  [[package]]
251
251
  name = "tensorfs-core"
252
- version = "0.3.0"
252
+ version = "0.3.1"
253
253
  dependencies = [
254
254
  "fs2",
255
255
  "minicbor",
@@ -258,7 +258,7 @@ dependencies = [
258
258
 
259
259
  [[package]]
260
260
  name = "tensorfs-py"
261
- version = "0.3.0"
261
+ version = "0.3.1"
262
262
  dependencies = [
263
263
  "pyo3",
264
264
  "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.0"
6
+ version = "0.3.1"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
@@ -1282,7 +1282,7 @@ pub fn cmd_worker(root: &Path, target: &str, f: &Flags) -> ExitCode {
1282
1282
  }
1283
1283
  let session = session_of(f, &p);
1284
1284
  let tenant = flag(f, "tenant").unwrap_or("dev").to_string();
1285
- let mut store = ok!(Store::init(root));
1285
+ let mut store = ok!(Store::open(root));
1286
1286
  if flag_on(f, "progress") {
1287
1287
  store = store.observed(Arc::new(Reporter::default()));
1288
1288
  }
@@ -1,5 +1,6 @@
1
1
  //! tfs — TensorFS store and CozyTensors inspection tool.
2
2
  //!
3
+ //! tfs version release version + sha256 of this binary, the skew observable
3
4
  //! tfs registry print the seeded platform namespace (alias -> digest)
4
5
  //! tfs id <file> object id (+ derived ids) of a stored document
5
6
  //! tfs cbor encode|decode|check <file> header conformance seam; JSON is diagnostic only
@@ -10,7 +11,15 @@
10
11
  //! --size <bytes> --small <n>, plus the dev-only kill points --fault <stage> --ready <path>.
11
12
  //!
12
13
  //! tfs store init|info <root>
14
+ //! tfs store ensure [<root>]
15
+ //! tfs store prepare-readers [<root>]
16
+ //! tfs store complete-cozytensors [<root>]
17
+ //! tfs home print TENSORFS_HOME or ~/.tensorfs
18
+ //! tfs home migrate <old-root> [--to <new-root>]
13
19
  //! tfs store rebuild <census> --rows <jsonl> [--sqlite <tensorfs.sqlite>]
20
+ //! tfs store admit-file <store> blob|manifest <id> <length> <source>
21
+ //! tfs repo-cache admit <cache> <store> blob|manifest <id> <length>
22
+ //! tfs repo-cache backfill <cache> <store> blob|manifest <id> <length>
14
23
  //! tfs put <root> <file> stream one object in (any shape)
15
24
  //! tfs get <root> <hex> verified read, whole or ranged
16
25
  //! tfs contains <root> <hex> the presence HINT, and what it is worth
@@ -94,6 +103,7 @@ use tensorfs_core::ingest::stamp::Stamp;
94
103
  use tensorfs_core::ingest::{Evidence, IngestProfile, IngestSubject, IngestVerificationReceipt};
95
104
  use tensorfs_core::manifest::{Draft, Entry, Manifest};
96
105
  use tensorfs_core::registry;
106
+ use tensorfs_core::repo_cache::{CacheKind, RepoObjectCache};
97
107
  use tensorfs_core::spec::EncodingSpec;
98
108
  use tensorfs_core::store::{Fault, Store, Verdict, FAULT_STAGES};
99
109
 
@@ -123,9 +133,30 @@ fn main() -> ExitCode {
123
133
  let args = accept_printed_ids(args);
124
134
  let a: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
125
135
  match a.as_slice() {
136
+ ["version"] => cmd_version(),
137
+ ["home"] => cmd_home(None),
138
+ ["home", "migrate", source] => cmd_home_migrate(Path::new(source), &flags),
139
+ ["home", r] => cmd_home(Some(Path::new(r))),
140
+ ["store", "init"] => cmd_store_default("init"),
141
+ ["store", "info"] => cmd_store_default("info"),
142
+ ["store", "ensure"] => cmd_store_default("ensure"),
143
+ ["store", "prepare-readers"] => cmd_store_prepare_readers(None),
144
+ ["store", "complete-cozytensors"] => cmd_store_complete_cozytensors(None),
126
145
  ["store", "init", r] => cmd_store(Path::new(r), "init"),
127
146
  ["store", "info", r] => cmd_store(Path::new(r), "info"),
147
+ ["store", "ensure", r] => cmd_store(Path::new(r), "ensure"),
148
+ ["store", "prepare-readers", r] => cmd_store_prepare_readers(Some(Path::new(r))),
149
+ ["store", "complete-cozytensors", r] => cmd_store_complete_cozytensors(Some(Path::new(r))),
128
150
  ["store", "rebuild", r] => repo::cmd_rebuild(Path::new(r), &flags),
151
+ ["store", "admit-file", store, kind, object, length, source] => {
152
+ cmd_store_admit_file(Path::new(store), kind, object, length, Path::new(source))
153
+ }
154
+ ["repo-cache", "admit", cache, store, kind, object, length] => {
155
+ cmd_repo_cache_admit(Path::new(cache), Path::new(store), kind, object, length)
156
+ }
157
+ ["repo-cache", "backfill", cache, store, kind, object, length] => {
158
+ cmd_repo_cache_backfill(Path::new(cache), Path::new(store), kind, object, length)
159
+ }
129
160
  ["put", r, f] => cmd_put(Path::new(r), Path::new(f), &flags),
130
161
  ["get", r, h] => cmd_get(Path::new(r), h, &flags),
131
162
  ["contains", r, h] => cmd_contains(Path::new(r), h),
@@ -255,6 +286,157 @@ fn main() -> ExitCode {
255
286
  }
256
287
  }
257
288
 
289
+ /// `tfs <version> sha256:<digest of this binary's own bytes>` — one line, machine-parsed by
290
+ /// tensorhub and cozy-creator at startup (tfs-051). Byte-identical builds print the same
291
+ /// digest; two builds stamped with one version cannot hide behind it.
292
+ fn cmd_version() -> ExitCode {
293
+ let bytes = match std::env::current_exe().and_then(fs::read) {
294
+ Ok(bytes) => bytes,
295
+ Err(error) => {
296
+ eprintln!("REFUSED IO_FAILED: read own binary: {error}");
297
+ return ExitCode::from(1);
298
+ }
299
+ };
300
+ println!(
301
+ "tfs {} sha256:{}",
302
+ env!("CARGO_PKG_VERSION"),
303
+ tensorfs_core::sha256::hex_digest(&bytes)
304
+ );
305
+ ExitCode::SUCCESS
306
+ }
307
+
308
+ fn cache_args(kind: &str, object: &str, length: &str) -> TfsResult<(CacheKind, ObjectRef)> {
309
+ let kind = CacheKind::parse(kind).ok_or_else(|| Refusal {
310
+ code: tensorfs_core::err::Code::WRONG_TYPE,
311
+ detail: format!("repo cache kind {kind:?} is not blob or manifest"),
312
+ })?;
313
+ let sha256 = tensorfs_core::ids::hex64("repo cache object", object)?;
314
+ let length = length.parse::<u64>().map_err(|_| Refusal {
315
+ code: tensorfs_core::err::Code::NUMBER_RANGE,
316
+ detail: format!("repo cache length {length:?} is not an unsigned integer"),
317
+ })?;
318
+ Ok((kind, ObjectRef { sha256, length }))
319
+ }
320
+
321
+ fn cmd_repo_cache_admit(
322
+ cache: &Path,
323
+ store: &Path,
324
+ kind: &str,
325
+ object: &str,
326
+ length: &str,
327
+ ) -> ExitCode {
328
+ let (kind, object) = match cache_args(kind, object, length) {
329
+ Ok(value) => value,
330
+ Err(error) => return bail(error),
331
+ };
332
+ let store = match Store::open(store) {
333
+ Ok(value) => value,
334
+ Err(error) => return bail(error),
335
+ };
336
+ let status = match RepoObjectCache::new(cache).admit(&store, kind, &object) {
337
+ Ok(value) => value,
338
+ Err(error) => return bail(error),
339
+ };
340
+ println!("{{\"status\":\"{}\"}}", status.as_str());
341
+ ExitCode::SUCCESS
342
+ }
343
+
344
+ fn cmd_repo_cache_backfill(
345
+ cache: &Path,
346
+ store: &Path,
347
+ kind: &str,
348
+ object: &str,
349
+ length: &str,
350
+ ) -> ExitCode {
351
+ let (kind, object) = match cache_args(kind, object, length) {
352
+ Ok(value) => value,
353
+ Err(error) => return bail(error),
354
+ };
355
+ let store = match Store::open(store) {
356
+ Ok(value) => value,
357
+ Err(error) => return bail(error),
358
+ };
359
+ let status = match RepoObjectCache::new(cache).backfill_store(&store, kind, &object) {
360
+ Ok(value) => value,
361
+ Err(error) => return bail(error),
362
+ };
363
+ println!("{{\"status\":\"{}\"}}", status.as_str());
364
+ ExitCode::SUCCESS
365
+ }
366
+
367
+ fn cmd_store_admit_file(
368
+ store: &Path,
369
+ kind: &str,
370
+ object: &str,
371
+ length: &str,
372
+ source: &Path,
373
+ ) -> ExitCode {
374
+ let (kind, object) = match cache_args(kind, object, length) {
375
+ Ok(value) => value,
376
+ Err(error) => return bail(error),
377
+ };
378
+ let store = match Store::open(store) {
379
+ Ok(value) => value,
380
+ Err(error) => return bail(error),
381
+ };
382
+ let admitted = match kind {
383
+ CacheKind::Blob => match store.put_file(source, Some(&object), &Fault::default()) {
384
+ Ok(value) => value.admitted,
385
+ Err(error) => return bail(error),
386
+ },
387
+ CacheKind::Manifest => {
388
+ let metadata = match fs::metadata(source) {
389
+ Ok(metadata) => metadata,
390
+ Err(error) => {
391
+ return bail(Refusal {
392
+ code: tensorfs_core::err::Code::IO_FAILED,
393
+ detail: format!("stat {}: {error}", source.display()),
394
+ })
395
+ }
396
+ };
397
+ if metadata.len() != object.length || object.length > Manifest::MAX_BYTES as u64 {
398
+ return bail(Refusal {
399
+ code: tensorfs_core::err::Code::LENGTH_MISMATCH,
400
+ detail: format!(
401
+ "{} is {} bytes, expected {}",
402
+ source.display(),
403
+ metadata.len(),
404
+ object.length
405
+ ),
406
+ });
407
+ }
408
+ let bytes = match fs::read(source) {
409
+ Ok(bytes) => bytes,
410
+ Err(error) => {
411
+ return bail(Refusal {
412
+ code: tensorfs_core::err::Code::IO_FAILED,
413
+ detail: format!("read {}: {error}", source.display()),
414
+ })
415
+ }
416
+ };
417
+ let manifest = match Manifest::parse(&bytes) {
418
+ Ok(value) => value,
419
+ Err(error) => return bail(error),
420
+ };
421
+ if ObjectRef::of(&bytes) != object {
422
+ return bail(Refusal {
423
+ code: tensorfs_core::err::Code::OBJECT_ID_MISMATCH,
424
+ detail: format!("{} bytes disagree with {}", source.display(), object.id()),
425
+ });
426
+ }
427
+ match store.put_manifest(&manifest) {
428
+ Ok(value) => value.admitted,
429
+ Err(error) => return bail(error),
430
+ }
431
+ }
432
+ };
433
+ println!(
434
+ "{{\"status\":\"{}\"}}",
435
+ if admitted { "stored" } else { "present" }
436
+ );
437
+ ExitCode::SUCCESS
438
+ }
439
+
258
440
  fn cmd_config_canonicalize(path: &Path, flags: &Flags) -> ExitCode {
259
441
  let Some(output) = flag(flags, "out") else {
260
442
  eprintln!("--out <canonical.json> is required");
@@ -336,9 +518,30 @@ mod repo;
336
518
  mod snap;
337
519
  mod watch;
338
520
 
521
+ fn cmd_home(explicit: Option<&Path>) -> ExitCode {
522
+ println!("{}", ok!(tensorfs_core::home::resolve(explicit)).display());
523
+ ExitCode::SUCCESS
524
+ }
525
+
526
+ fn cmd_home_migrate(source: &Path, flags: &Flags) -> ExitCode {
527
+ let destination = flag(flags, "to").map(Path::new);
528
+ let report = ok!(tensorfs_core::home::move_store(source, destination));
529
+ println!(
530
+ "moved {} -> {} (device/inode/mtime preserved by same-filesystem rename)",
531
+ report.source.display(),
532
+ report.destination.display(),
533
+ );
534
+ ExitCode::SUCCESS
535
+ }
536
+
537
+ fn cmd_store_default(verb: &str) -> ExitCode {
538
+ cmd_store(&ok!(tensorfs_core::home::resolve(None)), verb)
539
+ }
540
+
339
541
  fn cmd_store(root: &Path, verb: &str) -> ExitCode {
340
542
  let s = match verb {
341
543
  "init" => ok!(Store::init(root)),
544
+ "ensure" => ok!(Store::ensure(root)),
342
545
  _ => ok!(Store::open(root)),
343
546
  };
344
547
  println!("root: {}", s.root().display());
@@ -346,6 +549,24 @@ fn cmd_store(root: &Path, verb: &str) -> ExitCode {
346
549
  ExitCode::SUCCESS
347
550
  }
348
551
 
552
+ fn cmd_store_prepare_readers(explicit: Option<&Path>) -> ExitCode {
553
+ let root = ok!(tensorfs_core::home::resolve(explicit));
554
+ let store = ok!(Store::open(&root));
555
+ ok!(store.prepare_readers());
556
+ println!("root: {}", store.root().display());
557
+ println!("readers: prepared");
558
+ ExitCode::SUCCESS
559
+ }
560
+
561
+ fn cmd_store_complete_cozytensors(explicit: Option<&Path>) -> ExitCode {
562
+ let root = ok!(tensorfs_core::home::resolve(explicit));
563
+ let store = ok!(Store::open(&root));
564
+ for manifest in ok!(store.complete_cozytensors_manifests()) {
565
+ println!("{manifest}");
566
+ }
567
+ ExitCode::SUCCESS
568
+ }
569
+
349
570
  fn fault_of(f: &Flags) -> Fault {
350
571
  Fault {
351
572
  stage: flag(f, "fault").map(|s| s.to_string()),
@@ -0,0 +1,195 @@
1
+ //! TensorFS-owned default Store location.
2
+ //!
3
+ //! Core Store operations remain explicit-root. This resolver exists for user-facing entrypoints
4
+ //! and never consults another product's home or configuration.
5
+
6
+ use std::fs;
7
+ use std::os::unix::fs::MetadataExt;
8
+ use std::path::{Path, PathBuf};
9
+
10
+ use crate::catalog::WriterGuard;
11
+ use crate::err::{refuse, Code, Result};
12
+ use crate::store::{self, Store};
13
+
14
+ pub const ENV: &str = "TENSORFS_HOME";
15
+
16
+ #[derive(Debug, Clone, PartialEq, Eq)]
17
+ pub struct MoveReport {
18
+ pub source: PathBuf,
19
+ pub destination: PathBuf,
20
+ }
21
+
22
+ /// Resolve an optional caller-supplied Store root.
23
+ ///
24
+ /// Precedence is explicit argument, `TENSORFS_HOME`, then `$HOME/.tensorfs`.
25
+ pub fn resolve(explicit: Option<&Path>) -> Result<PathBuf> {
26
+ resolve_values(
27
+ explicit,
28
+ std::env::var_os(ENV).map(PathBuf::from).as_deref(),
29
+ std::env::var_os("HOME").map(PathBuf::from).as_deref(),
30
+ )
31
+ }
32
+
33
+ fn resolve_values(
34
+ explicit: Option<&Path>,
35
+ tensorfs_home: Option<&Path>,
36
+ user_home: Option<&Path>,
37
+ ) -> Result<PathBuf> {
38
+ if let Some(root) = explicit {
39
+ return nonempty("explicit TensorFS root", root);
40
+ }
41
+ if let Some(root) = tensorfs_home {
42
+ return nonempty(ENV, root);
43
+ }
44
+ match user_home {
45
+ Some(root) if !root.as_os_str().is_empty() => Ok(root.join(".tensorfs")),
46
+ _ => refuse(
47
+ Code::IO_FAILED,
48
+ "TensorFS root is absent: pass one explicitly, set TENSORFS_HOME, or set HOME",
49
+ ),
50
+ }
51
+ }
52
+
53
+ fn nonempty(source: &str, root: &Path) -> Result<PathBuf> {
54
+ if root.as_os_str().is_empty() {
55
+ return refuse(Code::IO_FAILED, format!("{source} is empty"));
56
+ }
57
+ Ok(root.to_path_buf())
58
+ }
59
+
60
+ /// Move one stopped Store into the resolved TensorFS home without copying or rehashing it.
61
+ ///
62
+ /// The destination must not exist and both parents must be on the same filesystem. The Store's
63
+ /// recovery lock refuses already-active readers and writers. This is still an offline operator
64
+ /// command: possible Store openers must be stopped so none can recreate the retired pathname
65
+ /// after the rename. No redirect or reservation is left at the old path.
66
+ pub fn move_store(source: &Path, explicit_destination: Option<&Path>) -> Result<MoveReport> {
67
+ let source_metadata = fs::symlink_metadata(source).map_err(|error| io("stat source", error))?;
68
+ if source_metadata.file_type().is_symlink() || !source_metadata.is_dir() {
69
+ return refuse(
70
+ Code::STORE_ERA,
71
+ format!("{} is not a TensorFS Store directory", source.display()),
72
+ );
73
+ }
74
+ let source = fs::canonicalize(source).map_err(|error| io("resolve source", error))?;
75
+ Store::open(&source)?;
76
+
77
+ let requested_destination = resolve(explicit_destination)?;
78
+ match fs::symlink_metadata(&requested_destination) {
79
+ Ok(_) => {
80
+ return refuse(
81
+ Code::IO_FAILED,
82
+ format!(
83
+ "destination {} already exists; remove the conflict or choose another root",
84
+ requested_destination.display()
85
+ ),
86
+ )
87
+ }
88
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
89
+ Err(error) => return Err(io("stat destination", error)),
90
+ }
91
+ let destination_name =
92
+ requested_destination
93
+ .file_name()
94
+ .ok_or_else(|| crate::err::Refusal {
95
+ code: Code::IO_FAILED,
96
+ detail: format!(
97
+ "destination {} has no final path component",
98
+ requested_destination.display()
99
+ ),
100
+ })?;
101
+ let destination_parent = requested_destination
102
+ .parent()
103
+ .filter(|path| !path.as_os_str().is_empty())
104
+ .unwrap_or_else(|| Path::new("."));
105
+ let destination_parent = fs::canonicalize(destination_parent)
106
+ .map_err(|error| io("resolve destination parent", error))?;
107
+ let destination = destination_parent.join(destination_name);
108
+ if source == destination {
109
+ return refuse(Code::IO_FAILED, "source and destination are the same Store");
110
+ }
111
+ let source_parent = source.parent().ok_or_else(|| crate::err::Refusal {
112
+ code: Code::IO_FAILED,
113
+ detail: format!("source {} has no parent", source.display()),
114
+ })?;
115
+ let source_device = fs::metadata(&source)
116
+ .map_err(|error| io("stat source", error))?
117
+ .dev();
118
+ let destination_device = fs::metadata(&destination_parent)
119
+ .map_err(|error| io("stat destination parent", error))?
120
+ .dev();
121
+ if source_device != destination_device {
122
+ return refuse(
123
+ Code::IO_FAILED,
124
+ format!(
125
+ "source {} and destination {} are on different filesystems; a hardcut requires an atomic rename",
126
+ source.display(),
127
+ destination.display()
128
+ ),
129
+ );
130
+ }
131
+
132
+ let _exclusive = WriterGuard::lock_rebuild(&source)?;
133
+ fs::rename(&source, &destination).map_err(|error| io("rename Store", error))?;
134
+ store::fsync_dir(&destination_parent)?;
135
+ if source_parent != destination_parent {
136
+ store::fsync_dir(source_parent)?;
137
+ }
138
+ Store::open(&destination)?;
139
+ Ok(MoveReport {
140
+ source,
141
+ destination,
142
+ })
143
+ }
144
+
145
+ fn io(what: &str, error: std::io::Error) -> crate::err::Refusal {
146
+ crate::err::Refusal {
147
+ code: Code::IO_FAILED,
148
+ detail: format!("{what}: {error}"),
149
+ }
150
+ }
151
+
152
+ #[cfg(test)]
153
+ mod tests {
154
+ use super::*;
155
+
156
+ #[test]
157
+ fn precedence_is_explicit_then_tensorfs_home_then_user_home() {
158
+ assert_eq!(
159
+ resolve_values(
160
+ Some(Path::new("/explicit")),
161
+ Some(Path::new("/environment")),
162
+ Some(Path::new("/user")),
163
+ )
164
+ .unwrap(),
165
+ Path::new("/explicit"),
166
+ );
167
+ assert_eq!(
168
+ resolve_values(
169
+ None,
170
+ Some(Path::new("/environment")),
171
+ Some(Path::new("/user"))
172
+ )
173
+ .unwrap(),
174
+ Path::new("/environment"),
175
+ );
176
+ assert_eq!(
177
+ resolve_values(None, None, Some(Path::new("/user"))).unwrap(),
178
+ Path::new("/user/.tensorfs"),
179
+ );
180
+ }
181
+
182
+ #[test]
183
+ fn an_empty_selected_value_refuses_instead_of_falling_through() {
184
+ assert_eq!(
185
+ resolve_values(None, Some(Path::new("")), Some(Path::new("/user")))
186
+ .unwrap_err()
187
+ .code,
188
+ Code::IO_FAILED,
189
+ );
190
+ assert_eq!(
191
+ resolve_values(None, None, None).unwrap_err().code,
192
+ Code::IO_FAILED,
193
+ );
194
+ }
195
+ }
@@ -18,6 +18,7 @@ pub mod fill;
18
18
  pub mod fit;
19
19
  pub mod gc;
20
20
  pub mod header;
21
+ pub mod home;
21
22
  pub mod ids;
22
23
  pub mod ingest;
23
24
  pub mod jcs;
@@ -30,6 +31,7 @@ pub mod read;
30
31
  pub mod receipt;
31
32
  pub mod registry;
32
33
  pub mod relation;
34
+ pub mod repo_cache;
33
35
  pub mod repository;
34
36
  pub mod sha256;
35
37
  pub mod spec;