tensorfs 0.3.40__tar.gz → 0.3.41__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 (131) hide show
  1. {tensorfs-0.3.40 → tensorfs-0.3.41}/Cargo.lock +2 -2
  2. {tensorfs-0.3.40 → tensorfs-0.3.41}/Cargo.toml +1 -1
  3. {tensorfs-0.3.40 → tensorfs-0.3.41}/PKG-INFO +1 -1
  4. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived.rs +13 -0
  5. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-py/src/store.rs +6 -1
  6. {tensorfs-0.3.40 → tensorfs-0.3.41}/pyproject.toml +1 -1
  7. {tensorfs-0.3.40 → tensorfs-0.3.41}/python/tensorfs/_derived_channel.py +45 -1
  8. tensorfs-0.3.41/python/tensorfs/_source_channel.py +105 -0
  9. {tensorfs-0.3.40 → tensorfs-0.3.41}/python/tensorfs/derived.py +26 -1
  10. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/Cargo.toml +0 -0
  11. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/build.rs +0 -0
  12. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
  13. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
  14. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
  15. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
  16. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/b64.rs +0 -0
  17. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
  18. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
  19. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
  20. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
  21. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
  22. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
  23. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
  24. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
  25. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
  26. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/cache_roots.rs +0 -0
  27. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/canon.rs +0 -0
  28. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/capability.rs +0 -0
  29. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/catalog.rs +0 -0
  30. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/cbor.rs +0 -0
  31. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/checkpoint.rs +0 -0
  32. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/corpus.rs +0 -0
  33. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
  34. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
  35. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
  36. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/progress.rs +0 -0
  37. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/resume_tests.rs +0 -0
  38. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/retention.rs +0 -0
  39. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/roots.rs +0 -0
  40. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/dtype.rs +0 -0
  41. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/durability.rs +0 -0
  42. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/err.rs +0 -0
  43. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/fetch.rs +0 -0
  44. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/fill.rs +0 -0
  45. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/fit.rs +0 -0
  46. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/gc.rs +0 -0
  47. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/header.rs +0 -0
  48. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/home.rs +0 -0
  49. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ids.rs +0 -0
  50. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
  51. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
  52. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
  53. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
  54. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
  55. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
  56. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
  57. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/source.rs +0 -0
  58. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
  59. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
  60. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/jcs.rs +0 -0
  61. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/lib.rs +0 -0
  62. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/limits.rs +0 -0
  63. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/machine.rs +0 -0
  64. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/manifest.rs +0 -0
  65. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/meta.rs +0 -0
  66. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/project.rs +0 -0
  67. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/providers.rs +0 -0
  68. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/read.rs +0 -0
  69. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/receipt.rs +0 -0
  70. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/reclaim.rs +0 -0
  71. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/registry.rs +0 -0
  72. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/relation.rs +0 -0
  73. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/repo_cache.rs +0 -0
  74. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/repository.rs +0 -0
  75. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/sha256.rs +0 -0
  76. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/source_artifact.rs +0 -0
  77. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/spec.rs +0 -0
  78. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/staging.rs +0 -0
  79. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/storage.rs +0 -0
  80. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/store.rs +0 -0
  81. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/api.rs +0 -0
  82. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/http.rs +0 -0
  83. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/hub.rs +0 -0
  84. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
  85. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/mod.rs +0 -0
  86. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/policy.rs +0 -0
  87. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
  88. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/pull.rs +0 -0
  89. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/push.rs +0 -0
  90. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
  91. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/tests.rs +0 -0
  92. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/src/vectors.rs +0 -0
  93. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
  94. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fit.rs +0 -0
  95. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
  96. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
  97. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
  98. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
  99. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
  100. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
  101. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
  102. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
  103. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/home.rs +0 -0
  104. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/import_tree.rs +0 -0
  105. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
  106. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/liveness.rs +0 -0
  107. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/read_plan.rs +0 -0
  108. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/reclaim.rs +0 -0
  109. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
  110. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
  111. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/repo_store.rs +0 -0
  112. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
  113. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
  114. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
  115. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
  116. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/source_plan.rs +0 -0
  117. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
  118. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
  119. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/staging.rs +0 -0
  120. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
  121. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
  122. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/version.rs +0 -0
  123. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-core/tests/write_through.rs +0 -0
  124. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-py/Cargo.toml +0 -0
  125. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-py/src/docs.rs +0 -0
  126. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-py/src/errors.rs +0 -0
  127. {tensorfs-0.3.40 → tensorfs-0.3.41}/crates/tensorfs-py/src/lib.rs +0 -0
  128. {tensorfs-0.3.40 → tensorfs-0.3.41}/python/tensorfs/__init__.py +0 -0
  129. {tensorfs-0.3.40 → tensorfs-0.3.41}/python/tensorfs/_ext.pyi +0 -0
  130. {tensorfs-0.3.40 → tensorfs-0.3.41}/python/tensorfs/errors.py +0 -0
  131. {tensorfs-0.3.40 → tensorfs-0.3.41}/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.40"
424
+ version = "0.3.41"
425
425
  dependencies = [
426
426
  "fs2",
427
427
  "minicbor",
@@ -434,7 +434,7 @@ dependencies = [
434
434
 
435
435
  [[package]]
436
436
  name = "tensorfs-py"
437
- version = "0.3.40"
437
+ version = "0.3.41"
438
438
  dependencies = [
439
439
  "pyo3",
440
440
  "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.40"
6
+ version = "0.3.41"
7
7
  edition = "2021"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tensorfs
3
- Version: 0.3.40
3
+ Version: 0.3.41
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Rust
@@ -2005,6 +2005,19 @@ fn inspect_retained_source(
2005
2005
  code: Code::ROOT_ABSENT,
2006
2006
  detail: "source inspection resolved no source".into(),
2007
2007
  })?;
2008
+ for component in &component_names {
2009
+ if !source
2010
+ .header
2011
+ .components
2012
+ .iter()
2013
+ .any(|(name, _)| name == component)
2014
+ {
2015
+ return refuse(
2016
+ Code::MISSING_FIELD,
2017
+ format!("source has no component {component:?}"),
2018
+ );
2019
+ }
2020
+ }
2008
2021
  let configs = config_names
2009
2022
  .iter()
2010
2023
  .map(|name| {
@@ -1864,7 +1864,12 @@ impl PyStore {
1864
1864
  let identity = source_facts(py, std::slice::from_ref(&inspection.view))?;
1865
1865
  out.set_item("source", identity.get_item(0)?)?;
1866
1866
  let component_rows = PyList::empty(py);
1867
- for component in &inspection.view.fact.components {
1867
+ // The selected fact is a normalized set. Author inspection must retain
1868
+ // the header's construction traversal, including nonlexical components.
1869
+ for (component, _) in &inspection.view.header.components {
1870
+ if !inspection.view.fact.components.contains(component) {
1871
+ continue;
1872
+ }
1868
1873
  let row = PyDict::new(py);
1869
1874
  row.set_item("name", component)?;
1870
1875
  row.set_item(
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "tensorfs"
7
- version = "0.3.40"
7
+ version = "0.3.41"
8
8
  description = "TensorFS — canonical tensor artifact substrate"
9
9
  requires-python = ">=3.12"
10
10
  classifiers = [
@@ -146,6 +146,19 @@ class DerivedTransaction:
146
146
  value.receipt = dict(receipt)
147
147
  return value
148
148
 
149
+ @classmethod
150
+ def accept(cls, fd: int) -> DerivedTransaction:
151
+ """Receive the owner's live-writer or validated committed-result decision."""
152
+ value = cls(fd)
153
+ receipt = value._call("receipt")
154
+ if receipt is not None:
155
+ if not isinstance(receipt, dict):
156
+ value.close()
157
+ raise ChannelRefusal("WRONG_TYPE", "native receipt must be an object")
158
+ value.receipt = receipt
159
+ value.close()
160
+ return value
161
+
149
162
  def _open(self) -> _Channel:
150
163
  if self._channel is None:
151
164
  raise ChannelRefusal("TRANSACTION_CLOSED", "derived handle is closed")
@@ -406,6 +419,7 @@ def serve_derived(
406
419
  check_current()
407
420
  else:
408
421
  if op not in (
422
+ "receipt",
409
423
  "completed_parts",
410
424
  "completed_configs",
411
425
  "checkpoint",
@@ -418,7 +432,9 @@ def serve_derived(
418
432
  _fields(frame, op, ())
419
433
  with admit_io(op, 0):
420
434
  check_current()
421
- if op == "completed_parts":
435
+ if op == "receipt":
436
+ result = None
437
+ elif op == "completed_parts":
422
438
  result = writer.completed_parts()
423
439
  elif op == "completed_configs":
424
440
  result = writer.completed_configs()
@@ -456,3 +472,31 @@ def serve_derived(
456
472
  finally:
457
473
  channel.close()
458
474
  writer.fence()
475
+
476
+
477
+ def serve_derived_replay(
478
+ receipt: Mapping[str, Any],
479
+ fd: int,
480
+ *,
481
+ check_current: Callable[[], None],
482
+ record_receipt: Callable[[dict[str, Any]], None],
483
+ ) -> None:
484
+ """Deliver retained native custody selected by the trusted execution owner."""
485
+ channel = _Channel(fd)
486
+ try:
487
+ request = channel.receive()
488
+ if not isinstance(request, dict):
489
+ raise ChannelRefusal("WRONG_TYPE", "expected a receipt request")
490
+ _fields(request, "receipt", ())
491
+ check_current()
492
+ facts = dict(receipt)
493
+ record_receipt(facts)
494
+ channel.send({"result": facts})
495
+ except EOFError:
496
+ return
497
+ except Exception as exc:
498
+ with contextlib.suppress(OSError, EOFError):
499
+ channel.send({"error": str(getattr(exc, "code", "IO_FAILED")), "detail": "native receipt owner refused"})
500
+ raise
501
+ finally:
502
+ channel.close()
@@ -0,0 +1,105 @@
1
+ """One exact source's bounded metadata/config channel, with no Store escape."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import threading
7
+ from collections.abc import Callable, Sequence
8
+ from typing import Any, ContextManager
9
+
10
+ from . import _ext
11
+ from ._derived_channel import ChannelRefusal, _Channel, _CHUNK, _METADATA, _fields
12
+ from .derived import Source, SourceInspection
13
+
14
+
15
+ class SourceClient:
16
+ def __init__(self, fd: int, source: Source) -> None:
17
+ self.channel = _Channel(fd)
18
+ self.source = source
19
+ self.lock = threading.Lock()
20
+
21
+ def close(self) -> None:
22
+ self.channel.close()
23
+
24
+ def inspect(self, components: Sequence[str], configs: Sequence[str]) -> SourceInspection:
25
+ with self.lock:
26
+ try:
27
+ self.channel.send({"op": "inspect", "components": list(components), "configs": list(configs)})
28
+ answer = self.channel.reply()
29
+ if set(answer) != {"result"} or not isinstance(answer["result"], dict):
30
+ raise ChannelRefusal("WRONG_TYPE", "invalid source inspection response")
31
+ value = answer["result"]
32
+ total = 0
33
+ for config in value["configs"]:
34
+ length = config.pop("length")
35
+ if type(length) is not int or not 0 <= length <= _METADATA - total:
36
+ raise ChannelRefusal("SIZE_CAP", "source configurations exceed their bound")
37
+ total += length
38
+ data = bytearray(length)
39
+ view = memoryview(data)
40
+ while view:
41
+ block = self.channel.receive()
42
+ if not isinstance(block, bytes) or not block or len(block) > len(view):
43
+ raise ChannelRefusal("LENGTH_MISMATCH", "invalid source configuration stream")
44
+ view[:len(block)] = block
45
+ view = view[len(block):]
46
+ config["data"] = bytes(data)
47
+ result = SourceInspection.from_native(value)
48
+ if result.source != self.source:
49
+ raise ChannelRefusal("TRANSACTION_CONFLICT", "source inspection changed identity")
50
+ return result
51
+ except BaseException:
52
+ self.channel.close()
53
+ raise
54
+
55
+
56
+ def serve_source(
57
+ store: _ext.Store,
58
+ source: Source,
59
+ fd: int,
60
+ *,
61
+ check_current: Callable[[], None],
62
+ admit_io: Callable[[], ContextManager[None]],
63
+ ) -> None:
64
+ """The owner fixes the source; the peer can only select its native metadata."""
65
+ channel = _Channel(fd)
66
+ try:
67
+ while True:
68
+ request = channel.receive()
69
+ if not isinstance(request, dict):
70
+ raise ChannelRefusal("WRONG_TYPE", "expected a source inspection")
71
+ _fields(request, "inspect", ("components", "configs"))
72
+ check_current()
73
+ components, configs = request["components"], request["configs"]
74
+ if not isinstance(components, list) or not isinstance(configs, list):
75
+ raise ChannelRefusal("WRONG_TYPE", "source selections must be lists")
76
+ with admit_io():
77
+ check_current()
78
+ if not components or not configs:
79
+ raw_header = store.manifest(source.manifest)["header"]
80
+ if raw_header is None:
81
+ raise ChannelRefusal("MISSING_FIELD", "source has no tensor header")
82
+ header = _ext.parse_header(raw_header)
83
+ components = components or list(header["components"])
84
+ configs = configs or list(header.get("configs", {}))
85
+ value = store.inspect_derived_source(source.manifest, source.length, components, configs)
86
+ check_current()
87
+ configuration = value["configs"]
88
+ metadata: dict[str, Any] = {
89
+ **value,
90
+ "configs": [{"name": row["name"], "length": len(row["data"])} for row in configuration],
91
+ }
92
+ channel.send({"result": metadata})
93
+ for row in configuration:
94
+ data = memoryview(row["data"])
95
+ for start in range(0, len(data), _CHUNK):
96
+ check_current()
97
+ channel.data(data[start:start+_CHUNK])
98
+ except EOFError:
99
+ return
100
+ except Exception as exc:
101
+ with contextlib.suppress(OSError, EOFError):
102
+ channel.send({"error": str(getattr(exc, "code", "IO_FAILED")), "detail": "source owner refused inspection"})
103
+ raise
104
+ finally:
105
+ channel.close()
@@ -11,7 +11,7 @@ from collections.abc import Callable, Mapping, Sequence
11
11
  from dataclasses import dataclass, field
12
12
  from typing import Any, Literal
13
13
 
14
- from ._derived_channel import DerivedTransaction, serve_derived
14
+ from ._derived_channel import DerivedTransaction, serve_derived, serve_derived_replay
15
15
 
16
16
 
17
17
  @dataclass(frozen=True, slots=True)
@@ -102,12 +102,32 @@ class SourceCapability(Source):
102
102
  _inspect: Callable[[Sequence[str], Sequence[str]], SourceInspection] = field(
103
103
  repr=False, compare=False
104
104
  )
105
+ _close: Callable[[], None] | None = field(default=None, repr=False, compare=False)
105
106
 
106
107
  def inspect(
107
108
  self, *, components: Sequence[str] = (), configs: Sequence[str] = ()
108
109
  ) -> SourceInspection:
109
110
  return self._inspect(tuple(components), tuple(configs))
110
111
 
112
+ @classmethod
113
+ def from_fd(cls, manifest: str, length: int, fd: int) -> SourceCapability:
114
+ """Own a preconnected inspector for one exact already-admitted source."""
115
+ from ._source_channel import SourceClient
116
+
117
+ client = SourceClient(fd, Source(manifest, length))
118
+ return cls(manifest, length, client.inspect, client.close)
119
+
120
+ def close(self) -> None:
121
+ if self._close is not None:
122
+ self._close()
123
+
124
+ def __enter__(self) -> SourceCapability:
125
+ return self
126
+
127
+ def __exit__(self, *exc: object) -> Literal[False]:
128
+ self.close()
129
+ return False
130
+
111
131
 
112
132
  @dataclass(frozen=True, slots=True)
113
133
  class Derivation:
@@ -191,6 +211,9 @@ def derive(output: OutputCapability, declaration: Derivation) -> DerivedTransact
191
211
  return output.open(declaration)
192
212
 
193
213
 
214
+ from ._source_channel import serve_source # noqa: E402
215
+
216
+
194
217
  __all__ = [
195
218
  "Source",
196
219
  "PartSource",
@@ -205,4 +228,6 @@ __all__ = [
205
228
  "DerivedTransaction",
206
229
  "derive",
207
230
  "serve_derived",
231
+ "serve_derived_replay",
232
+ "serve_source",
208
233
  ]