tensorfs 0.3.43__tar.gz → 0.3.44__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. {tensorfs-0.3.43 → tensorfs-0.3.44}/Cargo.lock +2 -2
  2. {tensorfs-0.3.43 → tensorfs-0.3.44}/Cargo.toml +1 -1
  3. {tensorfs-0.3.43 → tensorfs-0.3.44}/PKG-INFO +5 -1
  4. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/http.rs +37 -3
  5. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/tests.rs +130 -4
  6. {tensorfs-0.3.43 → tensorfs-0.3.44}/pyproject.toml +4 -1
  7. {tensorfs-0.3.43 → tensorfs-0.3.44}/python/tensorfs/__init__.py +25 -3
  8. tensorfs-0.3.44/python/tensorfs/_tensors.py +498 -0
  9. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/Cargo.toml +0 -0
  10. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/build.rs +0 -0
  11. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  12. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  13. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  14. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  15. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/b64.rs +0 -0
  16. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  17. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  18. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  19. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
  20. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
  21. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  22. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
  23. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  24. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  25. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/cache_roots.rs +0 -0
  26. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/canon.rs +0 -0
  27. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/capability.rs +0 -0
  28. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/catalog.rs +0 -0
  29. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/cbor.rs +0 -0
  30. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  31. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/checkpoint_root.rs +0 -0
  32. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/corpus.rs +0 -0
  33. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  34. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  35. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  36. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  37. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
  38. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/derived/retention.rs +0 -0
  39. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/derived/roots.rs +0 -0
  40. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/derived.rs +0 -0
  41. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/dtype.rs +0 -0
  42. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/durability.rs +0 -0
  43. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/err.rs +0 -0
  44. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/fetch.rs +0 -0
  45. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/fill.rs +0 -0
  46. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/fit.rs +0 -0
  47. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/gc.rs +0 -0
  48. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/header.rs +0 -0
  49. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/home.rs +0 -0
  50. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ids.rs +0 -0
  51. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  52. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
  53. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
  54. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  55. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/h3_lora.rs +0 -0
  56. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  57. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  58. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
  59. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/source.rs +0 -0
  60. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  61. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
  62. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/jcs.rs +0 -0
  63. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/lib.rs +0 -0
  64. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/limits.rs +0 -0
  65. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/machine.rs +0 -0
  66. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/manifest.rs +0 -0
  67. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/meta.rs +0 -0
  68. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/project.rs +0 -0
  69. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/providers.rs +0 -0
  70. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/read.rs +0 -0
  71. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/receipt.rs +0 -0
  72. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/reclaim.rs +0 -0
  73. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/registry.rs +0 -0
  74. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/relation.rs +0 -0
  75. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/repo_cache.rs +0 -0
  76. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/repository.rs +0 -0
  77. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/sha256.rs +0 -0
  78. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/source_artifact.rs +0 -0
  79. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/spec.rs +0 -0
  80. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/staging.rs +0 -0
  81. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/storage.rs +0 -0
  82. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/store.rs +0 -0
  83. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/api.rs +0 -0
  84. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/hub.rs +0 -0
  85. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  86. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  87. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  88. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
  89. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/pull.rs +0 -0
  90. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/push.rs +0 -0
  91. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
  92. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/src/vectors.rs +0 -0
  93. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/checkpoint_roots.rs +0 -0
  94. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  95. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fit.rs +0 -0
  96. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  97. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  98. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  99. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  100. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  101. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  102. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  103. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/h3_lora.rs +0 -0
  104. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  105. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/home.rs +0 -0
  106. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  107. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  108. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/liveness.rs +0 -0
  109. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/read_plan.rs +0 -0
  110. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  111. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  112. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  113. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  114. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  115. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  116. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  117. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  118. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  119. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
  120. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
  121. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/staging.rs +0 -0
  122. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
  123. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  124. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/version.rs +0 -0
  125. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-core/tests/write_through.rs +0 -0
  126. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-py/Cargo.toml +0 -0
  127. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-py/src/docs.rs +0 -0
  128. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-py/src/errors.rs +0 -0
  129. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-py/src/lib.rs +0 -0
  130. {tensorfs-0.3.43 → tensorfs-0.3.44}/crates/tensorfs-py/src/store.rs +0 -0
  131. {tensorfs-0.3.43 → tensorfs-0.3.44}/python/tensorfs/_derived_channel.py +0 -0
  132. {tensorfs-0.3.43 → tensorfs-0.3.44}/python/tensorfs/_ext.pyi +0 -0
  133. {tensorfs-0.3.43 → tensorfs-0.3.44}/python/tensorfs/_source_channel.py +0 -0
  134. {tensorfs-0.3.43 → tensorfs-0.3.44}/python/tensorfs/derived.py +0 -0
  135. {tensorfs-0.3.43 → tensorfs-0.3.44}/python/tensorfs/errors.py +0 -0
  136. {tensorfs-0.3.43 → tensorfs-0.3.44}/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.43"
424
+ version = "0.3.44"
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.43"
438
+ version = "0.3.44"
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.43"
6
+ version = "0.3.44"
7
7
  edition = "2021"
@@ -1,8 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.43
3
+ Version: 0.3.44
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
7
+ Requires-Dist: torch>=2.11 ; extra == 'torch'
8
+ Requires-Dist: torchao==0.18.0 ; extra == 'torch'
9
+ Requires-Dist: numpy>=1.26 ; extra == 'torch'
10
+ Provides-Extra: torch
7
11
  Summary: TensorFS — canonical tensor artifact substrate
8
12
  Requires-Python: >=3.12
@@ -21,6 +21,11 @@
21
21
  //! exactly zero bytes. A CONTROL-plane exchange now answers to `Judge::Transfer`: the
22
22
  //! pull's own progress, which is a measurement, and never to this call's own duration,
23
23
  //! which would be a constant.
24
+ //!
25
+ //! Object GETs also apply their existing measured stream-silence rule while
26
+ //! handshaking, writing and reading headers (h3a-070). Previously Judge::Stream
27
+ //! was ignored in those phases, so body retries were unreachable for a silent
28
+ //! TLS peer. Phase progress resets that phase's clock; no object wall limit is added.
24
29
 
25
30
  use crate::err::{refuse, Code, Refusal, Result};
26
31
  use crate::transport::ledger::{Deadline, Ledger};
@@ -122,6 +127,24 @@ fn transfer_verdict(
122
127
  )
123
128
  }
124
129
 
130
+ /// Object-stream silence is judged before the body too. Each phase starts with
131
+ /// its own window and advances only when transport bytes actually move; slow
132
+ /// progress may span arbitrarily many windows without exhausting a wall budget.
133
+ fn stream_verdict(judge: Judge, ledger: &Ledger, advanced: Instant, phase: &str) -> Result<()> {
134
+ if judge != Judge::Stream || advanced.elapsed() <= ledger.patience() {
135
+ return Ok(());
136
+ }
137
+ refuse(
138
+ Code::TRANSFER_FAILED,
139
+ format!(
140
+ "the object stream stopped during {phase}: silent {:.1} s against the \
141
+ {:.1} s this pull's measured pace allows",
142
+ advanced.elapsed().as_secs_f64(),
143
+ ledger.patience().as_secs_f64(),
144
+ ),
145
+ )
146
+ }
147
+
125
148
  pub struct Client {
126
149
  tls: Arc<rustls::ClientConfig>,
127
150
  }
@@ -283,6 +306,7 @@ fn tls_handshake(
283
306
  fail: Code,
284
307
  ) -> Result<()> {
285
308
  let sample = ledger.sample();
309
+ let mut advanced = Instant::now();
286
310
  while conn.is_handshaking() {
287
311
  transfer_verdict(judge, ledger, since, "a TLS handshake")?;
288
312
  let tick = match deadline.remaining()? {
@@ -293,8 +317,11 @@ fn tls_handshake(
293
317
  let _ = sock.set_write_timeout(Some(tick.max(Duration::from_millis(1))));
294
318
  if conn.wants_write() {
295
319
  match conn.write_tls(sock) {
320
+ Ok(n) if n > 0 => advanced = Instant::now(),
296
321
  Ok(_) => {}
297
- Err(e) if would_block(&e) => {}
322
+ Err(e) if would_block(&e) => {
323
+ stream_verdict(judge, ledger, advanced, "TLS handshake")?;
324
+ }
298
325
  Err(e) => return refuse(fail, format!("TLS handshake write: {e}")),
299
326
  }
300
327
  continue;
@@ -302,12 +329,15 @@ fn tls_handshake(
302
329
  match conn.read_tls(sock) {
303
330
  Ok(0) => return refuse(fail, "origin closed the connection mid-handshake"),
304
331
  Ok(_) => {
332
+ advanced = Instant::now();
305
333
  conn.process_new_packets().map_err(|e| Refusal {
306
334
  code: fail,
307
335
  detail: format!("TLS refused: {e}"),
308
336
  })?;
309
337
  }
310
- Err(e) if would_block(&e) => {}
338
+ Err(e) if would_block(&e) => {
339
+ stream_verdict(judge, ledger, advanced, "TLS handshake")?;
340
+ }
311
341
  Err(e) => return refuse(fail, format!("TLS handshake read: {e}")),
312
342
  }
313
343
  }
@@ -733,6 +763,7 @@ fn write_all(
733
763
  Err(e) => return refuse(fail, format!("the connection failed mid-write: {e}")),
734
764
  }
735
765
  }
766
+ stream_verdict(judge, ledger, advanced, "request write")?;
736
767
  if let Some(bound) = silence {
737
768
  if advanced.elapsed() > bound {
738
769
  return refuse(
@@ -811,6 +842,7 @@ fn read_step_ticking(
811
842
  since: Instant,
812
843
  fail: Code,
813
844
  ) -> Result<usize> {
845
+ let advanced = Instant::now();
814
846
  loop {
815
847
  transfer_verdict(judge, ledger, since, "a response head")?;
816
848
  let tick = match deadline.remaining()? {
@@ -820,7 +852,9 @@ fn read_step_ticking(
820
852
  wire.set_read_timeout(tick);
821
853
  match wire.read_step(buf) {
822
854
  Ok(n) => return Ok(n),
823
- Err(e) if would_block(&e) => {}
855
+ Err(e) if would_block(&e) => {
856
+ stream_verdict(judge, ledger, advanced, "response head")?;
857
+ }
824
858
  Err(e) => return refuse(fail, format!("the connection failed: {e}")),
825
859
  }
826
860
  }
@@ -57,6 +57,8 @@ impl Request {
57
57
  }
58
58
 
59
59
  enum Serve {
60
+ /// Read the request, then hold the socket without sending response headers.
61
+ StallHead,
60
62
  /// Content-Length framed whole answer.
61
63
  Whole {
62
64
  status: u16,
@@ -146,6 +148,11 @@ fn handle(mut socket: TcpStream, dispatch: Dispatch, hits: Arc<Mutex<HashMap<Str
146
148
  .entry(request.path.clone())
147
149
  .or_insert(0) += 1;
148
150
  match dispatch(&request) {
151
+ Serve::StallHead => {
152
+ let mut sink = [0u8; 16];
153
+ let _ = socket.set_read_timeout(Some(Duration::from_secs(30)));
154
+ let _ = socket.read(&mut sink);
155
+ }
149
156
  Serve::Whole {
150
157
  status,
151
158
  body,
@@ -915,6 +922,120 @@ fn black_holed_stream_is_stalled_by_the_ledger_and_reasked() {
915
922
  let _ = std::fs::remove_dir_all(root);
916
923
  }
917
924
 
925
+ fn prebody_silence_is_judged(https: bool) {
926
+ let listener = TcpListener::bind("127.0.0.1:0").unwrap();
927
+ let port = listener.local_addr().unwrap().port();
928
+ std::thread::spawn(move || {
929
+ let (mut socket, _) = listener.accept().unwrap();
930
+ socket
931
+ .set_read_timeout(Some(Duration::from_secs(3)))
932
+ .unwrap();
933
+ // Accept HTTP request bytes or a TLS ClientHello, but never answer.
934
+ let mut bytes = [0u8; 8192];
935
+ while matches!(socket.read(&mut bytes), Ok(n) if n > 0) {}
936
+ });
937
+ let scheme = if https { "https" } else { "http" };
938
+ let checked = local_policy()
939
+ .check(&format!("{scheme}://127.0.0.1:{port}/object"))
940
+ .unwrap();
941
+ let ledger = Ledger::with_resolution(Duration::from_millis(25));
942
+ ledger.moved(1024); // A pull that has already moved bytes, as on the real pod.
943
+ let response = super::http::request(
944
+ &super::http::Client::new(),
945
+ &checked,
946
+ "GET",
947
+ &[],
948
+ None,
949
+ Deadline::after_seconds(Some(1.0)),
950
+ &ledger,
951
+ super::http::Judge::Stream,
952
+ None,
953
+ Code::TRANSFER_FAILED,
954
+ );
955
+ let refusal = match response {
956
+ Err(refusal) => refusal,
957
+ Ok(_) => panic!("silent origin unexpectedly answered"),
958
+ };
959
+ assert_eq!(refusal.code, Code::TRANSFER_FAILED, "{refusal}");
960
+ assert!(
961
+ refusal.detail.contains(if https {
962
+ "TLS handshake"
963
+ } else {
964
+ "response head"
965
+ }),
966
+ "{refusal}"
967
+ );
968
+ }
969
+
970
+ #[test]
971
+ fn an_object_stalled_before_headers_is_judged_by_stream_progress() {
972
+ prebody_silence_is_judged(false);
973
+ }
974
+
975
+ #[test]
976
+ fn an_object_stalled_during_tls_is_judged_by_stream_progress() {
977
+ prebody_silence_is_judged(true);
978
+ }
979
+
980
+ #[test]
981
+ fn a_prebody_stall_is_reasked_without_discarding_completed_objects() {
982
+ let root = temporary("head-stall-retry");
983
+ let store = Store::init(&root).unwrap();
984
+ let checkpoint = Arc::new(checkpoint(2, 64 * 1024));
985
+ let victim = checkpoint.objects[0].sha256.clone();
986
+ let selected = victim.clone();
987
+ let origin = hub_origin(Arc::clone(&checkpoint), move |hex, attempt| {
988
+ (hex == selected && attempt == 1).then_some(Serve::StallHead)
989
+ });
990
+ let policy = SourcePolicy::default();
991
+ let report = pull(&request_for(&store, &origin.base, &policy)).unwrap();
992
+ assert_eq!(report.fetched, 4);
993
+ for object in &checkpoint.objects {
994
+ assert!(store.record_valid(&object.sha256).is_ok());
995
+ assert_eq!(
996
+ origin.hits(&format!("/obj/{}", object.sha256)),
997
+ if object.sha256 == victim { 2 } else { 1 }
998
+ );
999
+ }
1000
+ let _ = std::fs::remove_dir_all(root);
1001
+ }
1002
+
1003
+ #[test]
1004
+ fn fragmented_headers_can_progress_across_multiple_stream_windows() {
1005
+ let listener = TcpListener::bind("127.0.0.1:0").unwrap();
1006
+ let port = listener.local_addr().unwrap().port();
1007
+ std::thread::spawn(move || {
1008
+ let (mut socket, _) = listener.accept().unwrap();
1009
+ read_request(&mut socket).unwrap();
1010
+ socket.write_all(b"HTTP/1.1 200 OK\r\n").unwrap();
1011
+ for _ in 0..10 {
1012
+ std::thread::sleep(Duration::from_millis(50));
1013
+ socket.write_all(b"X-Progress: yes\r\n").unwrap();
1014
+ }
1015
+ socket.write_all(b"Content-Length: 3\r\n\r\nabc").unwrap();
1016
+ });
1017
+ let checked = local_policy()
1018
+ .check(&format!("http://127.0.0.1:{port}/object"))
1019
+ .unwrap();
1020
+ let ledger = Ledger::with_resolution(Duration::from_millis(25));
1021
+ let started = std::time::Instant::now();
1022
+ let response = super::http::request(
1023
+ &super::http::Client::new(),
1024
+ &checked,
1025
+ "GET",
1026
+ &[],
1027
+ None,
1028
+ Deadline::none(),
1029
+ &ledger,
1030
+ super::http::Judge::Stream,
1031
+ None,
1032
+ Code::TRANSFER_FAILED,
1033
+ )
1034
+ .unwrap();
1035
+ assert_eq!(response.read_capped(3).unwrap(), b"abc");
1036
+ assert!(started.elapsed() > ledger.floor() * 2);
1037
+ }
1038
+
918
1039
  #[test]
919
1040
  fn slow_origin_teaches_the_ledger_and_is_left_alone() {
920
1041
  let root = temporary("slow");
@@ -923,21 +1044,26 @@ fn slow_origin_teaches_the_ledger_and_is_left_alone() {
923
1044
  let slow = checkpoint.objects[0].sha256.clone();
924
1045
  let slow_for = slow.clone();
925
1046
  let bodies = checkpoint.bodies.clone();
926
- // 400 ms to first byte teaches the ledger this origin pauses; the 200 ms drip gaps
927
- // that follow are far over the 150 ms noise floor and must NOT be called stalls.
1047
+ // A successful 400 ms first answer teaches this origin's latency; subsequent
1048
+ // 800 ms gaps exceed the initial floor but remain below that measured term.
1049
+ // Unlike the old body-only rule,
1050
+ // initial pre-header silence is now bounded, so this fixture gives the first
1051
+ // answer a sufficient 600 ms floor. Production timing constants are unchanged.
928
1052
  let origin = hub_origin(Arc::clone(&checkpoint), move |hex, _| {
929
1053
  if hex == slow_for {
930
1054
  return Some(Serve::Drip {
931
1055
  body: bodies[hex].clone(),
932
1056
  chunk: 1024,
933
- gap: Duration::from_millis(200),
1057
+ gap: Duration::from_millis(800),
934
1058
  ttfb: Duration::from_millis(400),
935
1059
  });
936
1060
  }
937
1061
  None
938
1062
  });
939
1063
  let policy = SourcePolicy::default();
940
- let report = pull(&request_for(&store, &origin.base, &policy)).unwrap();
1064
+ let mut request = request_for(&store, &origin.base, &policy);
1065
+ request.sample_seconds = 0.1;
1066
+ let report = pull(&request).unwrap();
941
1067
  assert_eq!(report.fetched, 3);
942
1068
  assert_eq!(origin.hits(&format!("/obj/{slow}")), 1); // one ask; never condemned
943
1069
  let _ = std::fs::remove_dir_all(root);
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "tensorfs"
7
- version = "0.3.43"
7
+ version = "0.3.44"
8
8
  description = "TensorFS — canonical tensor artifact substrate"
9
9
  requires-python = ">=3.12"
10
10
  classifiers = [
@@ -13,6 +13,9 @@ classifiers = [
13
13
  "Programming Language :: Rust",
14
14
  ]
15
15
 
16
+ [project.optional-dependencies]
17
+ torch = ["torch>=2.11", "torchao==0.18.0", "numpy>=1.26"]
18
+
16
19
  [tool.maturin]
17
20
  manifest-path = "crates/tensorfs-py/Cargo.toml"
18
21
  module-name = "tensorfs._ext"
@@ -4,8 +4,9 @@ Marshal only. Every byte-level fact — hashing, canonical bytes, geometry, veri
4
4
  leases, the reader pool — comes from the extension, and `scripts/fence.py` enforces that
5
5
  this package computes none of them. What lives here is types and names.
6
6
 
7
- Torch is OUTSIDE the boundary. A read fills a buffer YOU own and hands back plain data;
8
- element sizes come from `DTYPES`, which is the compiled dtype enum, exported once::
7
+ The default inference reader returns owned Torch tensors (install `tensorfs[torch]`).
8
+ Torch loads only when tensor access is requested. Storage and metadata remain lightweight.
9
+ The lower-level read interface still fills caller-owned buffers::
9
10
 
10
11
  import torch, tensorfs
11
12
 
@@ -35,7 +36,21 @@ from dataclasses import dataclass
35
36
  from typing import Literal, TypedDict, cast
36
37
 
37
38
  from . import errors
38
- from .derived import Derivation, OutputCapability, SourceCapability, SourceInspection, derive
39
+ from .derived import (
40
+ Derivation,
41
+ OutputCapability,
42
+ SourceCapability,
43
+ SourceInspection,
44
+ derive,
45
+ )
46
+ from ._tensors import (
47
+ TensorAccessError,
48
+ TensorAllocation,
49
+ TensorReader,
50
+ from_parts,
51
+ physical_tensors,
52
+ tensors,
53
+ )
39
54
  from ._ext import (
40
55
  _fit,
41
56
  Batch,
@@ -182,12 +197,19 @@ def fit(
182
197
  ),
183
198
  )
184
199
 
200
+
185
201
  #: The closed carrier/logical dtype enum and its element sizes. The ONE export of dtype
186
202
  #: knowledge: a consumer sizing a `torch.frombuffer` reads it from here, never from a
187
203
  #: table of its own.
188
204
  DTYPES: dict[str, int] = dtypes()
189
205
 
190
206
  __all__ = [
207
+ "TensorAccessError",
208
+ "TensorAllocation",
209
+ "TensorReader",
210
+ "from_parts",
211
+ "physical_tensors",
212
+ "tensors",
191
213
  "Derivation",
192
214
  "OutputCapability",
193
215
  "SourceCapability",