x4d-devkit 0.46.0rc2__tar.gz → 0.46.0rc3__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 (135) hide show
  1. {x4d_devkit-0.46.0rc2/x4d_devkit.egg-info → x4d_devkit-0.46.0rc3}/PKG-INFO +1 -1
  2. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/pyproject.toml +1 -1
  3. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_clean_basemap.py +98 -0
  4. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_sdk_api.py +1 -1
  5. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/clean_basemap.py +40 -9
  6. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3/x4d_devkit.egg-info}/PKG-INFO +1 -1
  7. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/LICENSE +0 -0
  8. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/README.md +0 -0
  9. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/setup.cfg +0 -0
  10. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_annotation_contract.py +0 -0
  11. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_annotation_reference.py +0 -0
  12. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_assets_client.py +0 -0
  13. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_boundary.py +0 -0
  14. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_box.py +0 -0
  15. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_build_info.py +0 -0
  16. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_clean_basemap_client.py +0 -0
  17. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_cli_auth.py +0 -0
  18. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_client_json_contract.py +0 -0
  19. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_client_projects.py +0 -0
  20. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_clip_archives.py +0 -0
  21. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_contract_validation.py +0 -0
  22. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_coordinate_frames.py +0 -0
  23. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_current_state.py +0 -0
  24. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_current_state_identity_consumers_example.py +0 -0
  25. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_dataset_contract.py +0 -0
  26. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_dataset_identity.py +0 -0
  27. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_detection_eval.py +0 -0
  28. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_evaluate.py +0 -0
  29. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_export_bundle.py +0 -0
  30. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_image_review.py +0 -0
  31. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_inference_input.py +0 -0
  32. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_inference_schemas.py +0 -0
  33. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_inference_service.py +0 -0
  34. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_label_schemas.py +0 -0
  35. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_lidar_materialization.py +0 -0
  36. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_loader.py +0 -0
  37. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_matching.py +0 -0
  38. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_metrics.py +0 -0
  39. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_models.py +0 -0
  40. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_platform_pose.py +0 -0
  41. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_preannotation_client.py +0 -0
  42. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_reconstruction_client.py +0 -0
  43. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_release_gate.py +0 -0
  44. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_roadmark_preview.py +0 -0
  45. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_semantic_scene_authoring.py +0 -0
  46. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_semantic_scene_client.py +0 -0
  47. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_semantic_scene_contract.py +0 -0
  48. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_semantic_scene_independent.py +0 -0
  49. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_semantic_scene_native_clip.py +0 -0
  50. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_source_attributes.py +0 -0
  51. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_time_contract.py +0 -0
  52. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_token.py +0 -0
  53. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_tracking_eval.py +0 -0
  54. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_training_identity.py +0 -0
  55. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_transform.py +0 -0
  56. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_transform_chain.py +0 -0
  57. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_validation.py +0 -0
  58. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_vector_map.py +0 -0
  59. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_vector_map_geometry.py +0 -0
  60. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_vector_map_session.py +0 -0
  61. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_work_session.py +0 -0
  62. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/tests/test_work_session_validation_reuse.py +0 -0
  63. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/__init__.py +0 -0
  64. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/annotation_contract.py +0 -0
  65. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/annotation_reference.py +0 -0
  66. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/build_info.py +0 -0
  67. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/cli.py +0 -0
  68. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/__init__.py +0 -0
  69. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/_origin.py +0 -0
  70. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/annotation_reference.py +0 -0
  71. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/assets.py +0 -0
  72. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/bags.py +0 -0
  73. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/capabilities.py +0 -0
  74. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/checkpoints.py +0 -0
  75. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/clean_basemap.py +0 -0
  76. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/client.py +0 -0
  77. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/clip_work_sessions.py +0 -0
  78. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/clips.py +0 -0
  79. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/dataset_identity.py +0 -0
  80. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/image_review.py +0 -0
  81. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/label_schemas.py +0 -0
  82. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/preannotation.py +0 -0
  83. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/projects.py +0 -0
  84. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/reconstruction.py +0 -0
  85. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/semantic_scene.py +0 -0
  86. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  87. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/services.py +0 -0
  88. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/client/test_sets.py +0 -0
  89. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/consumer_identity.py +0 -0
  90. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/coordinate_contract.py +0 -0
  91. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/core/__init__.py +0 -0
  92. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/core/loader.py +0 -0
  93. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/core/models.py +0 -0
  94. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/core/token.py +0 -0
  95. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/core/transform.py +0 -0
  96. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/current_state.py +0 -0
  97. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/dataset_contract.py +0 -0
  98. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/dataset_identity.py +0 -0
  99. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/eval/__init__.py +0 -0
  100. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/eval/api.py +0 -0
  101. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/eval/box.py +0 -0
  102. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/eval/detection.py +0 -0
  103. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/eval/matching.py +0 -0
  104. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/eval/metrics.py +0 -0
  105. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/eval/tracking.py +0 -0
  106. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/export_bundle.py +0 -0
  107. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/image_review.py +0 -0
  108. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/inference/__init__.py +0 -0
  109. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/inference/input.py +0 -0
  110. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/inference/models.py +0 -0
  111. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/inference/schemas.py +0 -0
  112. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/inference/service.py +0 -0
  113. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/inference/tasks.py +0 -0
  114. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/json_contract.py +0 -0
  115. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/label_schema.py +0 -0
  116. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/lidar_materialization.py +0 -0
  117. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/materialize.py +0 -0
  118. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/roadmark_preview.py +0 -0
  119. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/semantic_scene_authoring.py +0 -0
  120. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/semantic_scene_contract.py +0 -0
  121. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/time_contract.py +0 -0
  122. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/training_identity.py +0 -0
  123. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/validation/__init__.py +0 -0
  124. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/validation/contract.py +0 -0
  125. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/validation/platform_pose.py +0 -0
  126. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/validation/report.py +0 -0
  127. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/validation/validator.py +0 -0
  128. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/vector_map.py +0 -0
  129. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/vector_map_geometry.py +0 -0
  130. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit/work_session.py +0 -0
  131. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit.egg-info/SOURCES.txt +0 -0
  132. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  133. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit.egg-info/entry_points.txt +0 -0
  134. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit.egg-info/requires.txt +0 -0
  135. {x4d_devkit-0.46.0rc2 → x4d_devkit-0.46.0rc3}/x4d_devkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: x4d-devkit
3
- Version: 0.46.0rc2
3
+ Version: 0.46.0rc3
4
4
  Summary: Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation
5
5
  Author: windzu
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "x4d-devkit"
7
- version = "0.46.0rc2"
7
+ version = "0.46.0rc3"
8
8
  description = "Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -3,6 +3,7 @@ import copy
3
3
  import numpy as np
4
4
  import pytest
5
5
  import zstandard
6
+ import x4d_devkit.clean_basemap as clean_basemap
6
7
 
7
8
  from tests.semantic_scene_contract_fixtures import current_semantic_scene_component
8
9
  from x4d_devkit.clean_basemap import (
@@ -189,6 +190,103 @@ def test_complete_scene_and_exact_original_observation_roundtrip():
189
190
  parsed.data["profile"]["voxel_size_m"] = 1
190
191
 
191
192
 
193
+ def test_decision_proof_reuse_is_within_one_validation_call(monkeypatch):
194
+ manifest, assets, _ = fixture()
195
+ decoded = []
196
+ original = clean_basemap.decode_clean_basemap_decisions
197
+
198
+ def count(*args, **kwargs):
199
+ result = original(*args, **kwargs)
200
+ assert not result.flags.writeable
201
+ decoded.append(result.nbytes)
202
+ return result
203
+
204
+ monkeypatch.setattr(clean_basemap, "decode_clean_basemap_decisions", count)
205
+ validate(manifest, assets)
206
+ assert decoded == [5]
207
+ validate(manifest, assets)
208
+ assert decoded == [5, 5]
209
+
210
+
211
+ @pytest.mark.parametrize("changed", [b"tampered", bytearray, memoryview])
212
+ def test_cached_decision_proof_still_checks_every_carrier_read(changed):
213
+ manifest, assets, _ = fixture()
214
+ original = original_points(manifest, assets)
215
+ path = manifest["sweep_sources"][0]["decisions"]["path"]
216
+ reads = 0
217
+
218
+ def read_asset(name):
219
+ nonlocal reads
220
+ body = assets[name]
221
+ if name == path:
222
+ reads += 1
223
+ if reads > 1:
224
+ return changed(body) if callable(changed) else changed
225
+ return body
226
+
227
+ with pytest.raises(ValueError, match="payload size/hash"):
228
+ validate_clean_basemap_resource(
229
+ manifest,
230
+ read_asset=read_asset,
231
+ observation_to_world=lambda sweep, ordinals, times: original[ordinals],
232
+ )
233
+
234
+
235
+ def add_second_sweep(manifest, assets):
236
+ source = copy.deepcopy(manifest["sweep_sources"][0])
237
+ source.update(sample_data_token="sweep-1", asset_sha256="sha256:" + "c" * 64)
238
+ carrier = encode_clean_basemap_decisions(
239
+ asset_sha256=source["asset_sha256"], decisions=[1, 0, 3, 18, 33]
240
+ )
241
+ source["decisions"] = carrier.descriptor("decisions")
242
+ assets[source["decisions"]["path"]] = carrier.payload
243
+ manifest["sweep_sources"].append(source)
244
+ manifest["diagnostics"]["source_point_count"] *= 2
245
+ manifest["diagnostics"]["decision_counts"] = {
246
+ key: value * 2
247
+ for key, value in manifest["diagnostics"]["decision_counts"].items()
248
+ }
249
+ manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
250
+ return source
251
+
252
+
253
+ @pytest.mark.parametrize("changed", ["source", "count", "descriptor"])
254
+ def test_decision_cache_never_reuses_a_different_binding(changed):
255
+ manifest, assets, _ = fixture()
256
+ second = add_second_sweep(manifest, assets)
257
+ second["decisions"] = copy.deepcopy(manifest["sweep_sources"][0]["decisions"])
258
+ if changed != "source":
259
+ second["asset_sha256"] = manifest["sweep_sources"][0]["asset_sha256"]
260
+ if changed == "count":
261
+ second["point_count"] += 1
262
+ manifest["diagnostics"]["source_point_count"] += 1
263
+ manifest["diagnostics"]["decision_counts"]["33"] += 1
264
+ if changed == "descriptor":
265
+ second["decisions"]["size_bytes"] += 1
266
+ manifest["basemap_revision"] = compute_clean_basemap_revision(manifest)
267
+ with pytest.raises(ValueError):
268
+ validate(manifest, assets)
269
+
270
+
271
+ @pytest.mark.parametrize("limit,expected_decodes", [(0, 3), (4, 3), (5, 3), (10, 2)])
272
+ def test_decision_proof_cache_is_bounded_and_evicted_entries_are_revalidated(
273
+ monkeypatch, limit, expected_decodes
274
+ ):
275
+ manifest, assets, _ = fixture()
276
+ add_second_sweep(manifest, assets)
277
+ original = clean_basemap.decode_clean_basemap_decisions
278
+ calls = []
279
+
280
+ def count(*args, **kwargs):
281
+ calls.append(kwargs["asset_sha256"])
282
+ return original(*args, **kwargs)
283
+
284
+ monkeypatch.setattr(clean_basemap, "_DECISION_CACHE_BYTES", limit)
285
+ monkeypatch.setattr(clean_basemap, "decode_clean_basemap_decisions", count)
286
+ validate(manifest, assets)
287
+ assert len(calls) == expected_decodes
288
+
289
+
192
290
  @pytest.mark.parametrize(
193
291
  "overrides,message",
194
292
  [
@@ -11,7 +11,7 @@ from x4d_devkit.eval.matching import MetricData
11
11
 
12
12
 
13
13
  def test_package_version_is_current_release():
14
- assert x4d_devkit.__version__ == "0.46.0rc2"
14
+ assert x4d_devkit.__version__ == "0.46.0rc3"
15
15
 
16
16
 
17
17
  def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
@@ -7,6 +7,7 @@ their native authorities; this module never guesses ownership from coordinates.
7
7
  from __future__ import annotations
8
8
 
9
9
  import copy
10
+ from collections import OrderedDict
10
11
  from dataclasses import dataclass
11
12
  import math
12
13
  import re
@@ -39,6 +40,7 @@ CLEAN_BASEMAP_CONTRACT_VERSION = "x4d-clean-static-basemap-v4"
39
40
  CLEAN_BASEMAP_OPERATION = "reconstruction.prepare_clean_static_basemap"
40
41
  CLEAN_BASEMAP_MAPPING_FORMAT = "x4d-clean-static-basemap-mapping-v2"
41
42
  CLEAN_BASEMAP_DECISION_FORMAT = "x4d-clean-static-basemap-decisions-v3"
43
+ _DECISION_CACHE_BYTES = 128 << 20
42
44
  CLEAN_BASEMAP_INPUT_FIELDS = frozenset(
43
45
  {"samples_revision", "sweeps_revision", "calibration_revision", "pose_revision"}
44
46
  )
@@ -611,13 +613,47 @@ def validate_clean_basemap_resource(
611
613
  for sweep in data["sweep_sources"]
612
614
  ]
613
615
  actual_counts = {str(code): 0 for code in DECISION_CODES}
614
- for sweep in data["sweep_sources"]:
616
+ decisions_cache = OrderedDict()
617
+ decisions_bytes = 0
618
+
619
+ def read_decisions(sweep):
620
+ nonlocal decisions_bytes
621
+ descriptor = sweep["decisions"]
622
+ payload = read_asset(descriptor["path"])
623
+ # A cached proof never authorizes changed or non-byte carrier data.
624
+ if (
625
+ type(payload) is not bytes
626
+ or len(payload) != descriptor["size_bytes"]
627
+ or sha256_bytes(payload) != descriptor["sha256"]
628
+ ):
629
+ raise ValueError(
630
+ "clean basemap payload size/hash does not match descriptor"
631
+ )
632
+ key = (
633
+ tuple(sorted(descriptor.items())),
634
+ sweep["asset_sha256"],
635
+ sweep["point_count"],
636
+ )
637
+ values = decisions_cache.pop(key, None)
638
+ if values is not None:
639
+ decisions_cache[key] = values
640
+ return values
615
641
  values = decode_clean_basemap_decisions(
616
- read_asset(sweep["decisions"]["path"]),
617
- descriptor=sweep["decisions"],
642
+ payload,
643
+ descriptor=descriptor,
618
644
  asset_sha256=sweep["asset_sha256"],
619
645
  point_count=sweep["point_count"],
620
646
  )
647
+ if values.nbytes <= _DECISION_CACHE_BYTES:
648
+ while decisions_bytes + values.nbytes > _DECISION_CACHE_BYTES:
649
+ _, evicted = decisions_cache.popitem(last=False)
650
+ decisions_bytes -= evicted.nbytes
651
+ decisions_cache[key] = values
652
+ decisions_bytes += values.nbytes
653
+ return values
654
+
655
+ for sweep in data["sweep_sources"]:
656
+ values = read_decisions(sweep)
621
657
  counts = np.bincount(values, minlength=256)
622
658
  for code in DECISION_CODES:
623
659
  actual_counts[str(code)] += int(counts[code])
@@ -661,12 +697,7 @@ def validate_clean_basemap_resource(
661
697
  if np.any(times < low) or np.any(times > high):
662
698
  raise ValueError("mapping exact point time is outside its source sweep")
663
699
  _mark_unique(observation_seen[index], ordinals, "original observation")
664
- values = decode_clean_basemap_decisions(
665
- read_asset(sweep["decisions"]["path"]),
666
- descriptor=sweep["decisions"],
667
- asset_sha256=sweep["asset_sha256"],
668
- point_count=sweep["point_count"],
669
- )
700
+ values = read_decisions(sweep)
670
701
  if not np.array_equal(values[ordinals], mapping["decision"][selected]):
671
702
  raise ValueError(
672
703
  "mapping decision does not match its original observation"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: x4d-devkit
3
- Version: 0.46.0rc2
3
+ Version: 0.46.0rc3
4
4
  Summary: Official X-4D external SDK and CLI for datasets, platform APIs, training bridges, and automation
5
5
  Author: windzu
6
6
  License-Expression: Apache-2.0
File without changes
File without changes
File without changes