x4d-devkit 0.53.0rc3__tar.gz → 0.55.0__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 (176) hide show
  1. {x4d_devkit-0.53.0rc3/x4d_devkit.egg-info → x4d_devkit-0.55.0}/PKG-INFO +3 -3
  2. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/README.md +2 -2
  3. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/pyproject.toml +1 -1
  4. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_dataset_identity.py +266 -120
  5. x4d_devkit-0.55.0/tests/test_image_source.py +237 -0
  6. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_label_schemas.py +4 -0
  7. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_sdk_api.py +1 -1
  8. x4d_devkit-0.55.0/tests/test_source_content.py +100 -0
  9. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_work_session.py +17 -1
  10. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/cli.py +61 -0
  11. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/dataset_identity.py +16 -0
  12. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/dataset_identity.py +135 -13
  13. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_clip.py +34 -0
  14. x4d_devkit-0.55.0/x4d_devkit/image_source.py +490 -0
  15. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/materialize.py +349 -58
  16. x4d_devkit-0.55.0/x4d_devkit/source_content.py +282 -0
  17. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/validator.py +9 -2
  18. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0/x4d_devkit.egg-info}/PKG-INFO +3 -3
  19. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit.egg-info/SOURCES.txt +4 -0
  20. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/LICENSE +0 -0
  21. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/setup.cfg +0 -0
  22. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_accounts.py +0 -0
  23. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_annotation_contract.py +0 -0
  24. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_annotation_document_hash.py +0 -0
  25. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_annotation_reference.py +0 -0
  26. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_assets_client.py +0 -0
  27. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_boundary.py +0 -0
  28. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_box.py +0 -0
  29. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_build_info.py +0 -0
  30. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_category_audit.py +0 -0
  31. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_clean_basemap.py +0 -0
  32. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_clean_basemap_client.py +0 -0
  33. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_cli_auth.py +0 -0
  34. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_client_json_contract.py +0 -0
  35. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_client_projects.py +0 -0
  36. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_clip_archives.py +0 -0
  37. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_contract_validation.py +0 -0
  38. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_coordinate_frames.py +0 -0
  39. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_current_state.py +0 -0
  40. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_current_state_identity_consumers_example.py +0 -0
  41. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_dataset_contract.py +0 -0
  42. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_detection_eval.py +0 -0
  43. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_evaluate.py +0 -0
  44. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_export_bundle.py +0 -0
  45. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_clip.py +0 -0
  46. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_conditional_rules.py +0 -0
  47. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_enum_set.py +0 -0
  48. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_export.py +0 -0
  49. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_geometry.py +0 -0
  50. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_groups.py +0 -0
  51. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_preannotation.py +0 -0
  52. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_reference.py +0 -0
  53. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_review.py +0 -0
  54. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_schema_migration.py +0 -0
  55. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_schema_registry_client.py +0 -0
  56. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_schema_upgrade.py +0 -0
  57. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_work_session.py +0 -0
  58. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_inference_input.py +0 -0
  59. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_inference_schemas.py +0 -0
  60. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_inference_service.py +0 -0
  61. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_lidar_materialization.py +0 -0
  62. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_loader.py +0 -0
  63. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_matching.py +0 -0
  64. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_metrics.py +0 -0
  65. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_models.py +0 -0
  66. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_parse_equivalence.py +0 -0
  67. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_platform_pose.py +0 -0
  68. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_preannotation_client.py +0 -0
  69. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_project_discovery.py +0 -0
  70. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_reconstruction_client.py +0 -0
  71. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_release_gate.py +0 -0
  72. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_roadmark_preview.py +0 -0
  73. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_authoring.py +0 -0
  74. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_client.py +0 -0
  75. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_contract.py +0 -0
  76. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_independent.py +0 -0
  77. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_native_clip.py +0 -0
  78. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_source_attributes.py +0 -0
  79. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_time_contract.py +0 -0
  80. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_token.py +0 -0
  81. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_tracking_eval.py +0 -0
  82. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_training_identity.py +0 -0
  83. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_transform.py +0 -0
  84. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_transform_chain.py +0 -0
  85. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_validation.py +0 -0
  86. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_vector_map.py +0 -0
  87. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_vector_map_geometry.py +0 -0
  88. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_vector_map_session.py +0 -0
  89. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_work_session_validation_reuse.py +0 -0
  90. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/__init__.py +0 -0
  91. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/_image_contract.py +0 -0
  92. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/accounts.py +0 -0
  93. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/annotation_contract.py +0 -0
  94. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/annotation_reference.py +0 -0
  95. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/audit.py +0 -0
  96. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/build_info.py +0 -0
  97. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/clean_basemap.py +0 -0
  98. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/__init__.py +0 -0
  99. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/_origin.py +0 -0
  100. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/accounts.py +0 -0
  101. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/annotation_reference.py +0 -0
  102. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/assets.py +0 -0
  103. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/bags.py +0 -0
  104. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/capabilities.py +0 -0
  105. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/checkpoints.py +0 -0
  106. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/clean_basemap.py +0 -0
  107. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/client.py +0 -0
  108. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/clip_work_sessions.py +0 -0
  109. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/clips.py +0 -0
  110. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_clips.py +0 -0
  111. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_exports.py +0 -0
  112. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_preannotations.py +0 -0
  113. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_review.py +0 -0
  114. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_schemas.py +0 -0
  115. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_work_sessions.py +0 -0
  116. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/label_schemas.py +0 -0
  117. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/preannotation.py +0 -0
  118. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/projects.py +0 -0
  119. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/reconstruction.py +0 -0
  120. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/semantic_scene.py +0 -0
  121. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  122. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/services.py +0 -0
  123. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/test_sets.py +0 -0
  124. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/consumer_identity.py +0 -0
  125. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/coordinate_contract.py +0 -0
  126. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/__init__.py +0 -0
  127. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/loader.py +0 -0
  128. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/models.py +0 -0
  129. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/token.py +0 -0
  130. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/transform.py +0 -0
  131. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/current_state.py +0 -0
  132. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/dataset_contract.py +0 -0
  133. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/__init__.py +0 -0
  134. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/api.py +0 -0
  135. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/box.py +0 -0
  136. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/detection.py +0 -0
  137. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/matching.py +0 -0
  138. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/metrics.py +0 -0
  139. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/tracking.py +0 -0
  140. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/export_bundle.py +0 -0
  141. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_export.py +0 -0
  142. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_geometry.py +0 -0
  143. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_groups.py +0 -0
  144. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_preannotation.py +0 -0
  145. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_reference.py +0 -0
  146. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_review.py +0 -0
  147. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_schema.py +0 -0
  148. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_schema_migration.py +0 -0
  149. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_schema_upgrade.py +0 -0
  150. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_work_session.py +0 -0
  151. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/__init__.py +0 -0
  152. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/input.py +0 -0
  153. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/models.py +0 -0
  154. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/schemas.py +0 -0
  155. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/service.py +0 -0
  156. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/tasks.py +0 -0
  157. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/json_contract.py +0 -0
  158. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/label_schema.py +0 -0
  159. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/lidar_materialization.py +0 -0
  160. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/project_discovery.py +0 -0
  161. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/roadmark_preview.py +0 -0
  162. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/semantic_scene_authoring.py +0 -0
  163. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/semantic_scene_contract.py +0 -0
  164. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/time_contract.py +0 -0
  165. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/training_identity.py +0 -0
  166. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/__init__.py +0 -0
  167. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/contract.py +0 -0
  168. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/platform_pose.py +0 -0
  169. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/report.py +0 -0
  170. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/vector_map.py +0 -0
  171. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/vector_map_geometry.py +0 -0
  172. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/work_session.py +0 -0
  173. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  174. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit.egg-info/entry_points.txt +0 -0
  175. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit.egg-info/requires.txt +0 -0
  176. {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/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.53.0rc3
3
+ Version: 0.55.0
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
@@ -264,8 +264,8 @@ identity manifests, and update plans use one schema field only:
264
264
  `clip_label_schema` are not aliases and are rejected. A ready platform record
265
265
  without the complete identity is also rejected.
266
266
 
267
- `x4d-devkit dataset inspect` writes `x4d-remote-identity-v7`, and
268
- `x4d-devkit dataset diff` writes `x4d-update-plan-v9`. The v8 plan records the
267
+ `x4d-devkit dataset inspect` writes `x4d-remote-identity-v8`, and
268
+ `x4d-devkit dataset diff` writes `x4d-update-plan-v10`. The v10 plan records the
269
269
  requested materialization policy, each cache's reviewed samples/sweeps
270
270
  materialization revisions, and every Clip's reviewed target and local
271
271
  predecessor identities.
@@ -221,8 +221,8 @@ identity manifests, and update plans use one schema field only:
221
221
  `clip_label_schema` are not aliases and are rejected. A ready platform record
222
222
  without the complete identity is also rejected.
223
223
 
224
- `x4d-devkit dataset inspect` writes `x4d-remote-identity-v7`, and
225
- `x4d-devkit dataset diff` writes `x4d-update-plan-v9`. The v8 plan records the
224
+ `x4d-devkit dataset inspect` writes `x4d-remote-identity-v8`, and
225
+ `x4d-devkit dataset diff` writes `x4d-update-plan-v10`. The v10 plan records the
226
226
  requested materialization policy, each cache's reviewed samples/sweeps
227
227
  materialization revisions, and every Clip's reviewed target and local
228
228
  predecessor identities.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "x4d-devkit"
7
- version = "0.53.0rc3"
7
+ version = "0.55.0"
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"
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import copy
4
+ import hashlib
4
5
  import json
5
6
 
6
7
  import numpy as np
@@ -28,6 +29,11 @@ from x4d_devkit.dataset_identity import (
28
29
  update_plan_exit_code,
29
30
  )
30
31
  from x4d_devkit.label_schema import compute_label_schema_fingerprint
32
+ from x4d_devkit.source_content import (
33
+ SOURCE_CONTENT_MANIFEST_CONTRACT,
34
+ load_local_source_content,
35
+ source_content_revisions,
36
+ )
31
37
  from x4d_devkit.materialize import (
32
38
  load_materialized_cache_identity,
33
39
  materialize_work_session_clip,
@@ -42,6 +48,8 @@ def _identity(clip_id: str = "clip-a", **overrides):
42
48
  "source_revision": "sha256:context",
43
49
  "samples_revision": "sha256:" + "1" * 64,
44
50
  "sweeps_revision": "sha256:" + "2" * 64,
51
+ "samples_content_revision": "sha256:" + "7" * 64,
52
+ "sweeps_content_revision": "sha256:" + "8" * 64,
45
53
  "calibration_revision": "sha256:calibration",
46
54
  "pose_revision": "sha256:pose",
47
55
  "annotation_revision": "sha256:annotation",
@@ -392,32 +400,91 @@ class _FailedStream(_FakeStream):
392
400
  raise RuntimeError("fixture download failed")
393
401
 
394
402
 
403
+ def _asset_bytes(path: str, overrides: dict[str, bytes] | None = None) -> bytes:
404
+ """Deterministic per-path fixture content; overrides model changed bytes."""
405
+ if overrides and path in overrides:
406
+ return overrides[path]
407
+ if path.endswith(".bin"):
408
+ seed = int(hashlib.sha256(path.encode()).hexdigest()[:4], 16) % 97
409
+ return np.array(
410
+ [(1.0, 2.0, 3.0, seed / 100.0, 1000)],
411
+ dtype=[
412
+ ("x", "<f4"),
413
+ ("y", "<f4"),
414
+ ("z", "<f4"),
415
+ ("intensity", "<f4"),
416
+ ("timestamp_ns", "<u8"),
417
+ ],
418
+ ).tobytes()
419
+ return b"jpeg:" + path.encode()
420
+
421
+
422
+ def _bin_bytes(intensity: float) -> bytes:
423
+ return np.array(
424
+ [(1.0, 2.0, 3.0, intensity, 1000)],
425
+ dtype=[
426
+ ("x", "<f4"),
427
+ ("y", "<f4"),
428
+ ("z", "<f4"),
429
+ ("intensity", "<f4"),
430
+ ("timestamp_ns", "<u8"),
431
+ ],
432
+ ).tobytes()
433
+
434
+
435
+ def _url_path(url) -> str:
436
+ text = str(url)
437
+ marker = "/api/v1/files/"
438
+ return text.split(marker, 1)[1] if marker in text else text
439
+
440
+
395
441
  class _FakeHTTP:
396
- def __init__(self):
442
+ def __init__(self, overrides: dict[str, bytes] | None = None):
397
443
  self.urls = []
444
+ self.overrides = dict(overrides or {})
398
445
 
399
446
  def stream(self, method, url):
400
447
  self.urls.append((method, url))
401
- payload = (
402
- np.array(
403
- [(1.0, 2.0, 3.0, 0.5, 1000)],
404
- dtype=[
405
- ("x", "<f4"),
406
- ("y", "<f4"),
407
- ("z", "<f4"),
408
- ("intensity", "<f4"),
409
- ("timestamp_ns", "<u8"),
410
- ],
411
- ).tobytes()
412
- if str(url).endswith(".bin")
413
- else b"jpeg"
448
+ return _FakeStream(_asset_bytes(_url_path(url), self.overrides))
449
+
450
+
451
+ def _source_manifest(payload, overrides=None):
452
+ rows = []
453
+ for row in payload["sample_data"]:
454
+ data = _asset_bytes(row["file_path"], overrides)
455
+ rows.append(
456
+ {
457
+ "path": row["file_path"],
458
+ "size": len(data),
459
+ "sha256": "sha256:" + hashlib.sha256(data).hexdigest(),
460
+ }
414
461
  )
415
- return _FakeStream(payload)
462
+ rows.sort(key=lambda item: item["path"])
463
+ return {"contract_version": SOURCE_CONTENT_MANIFEST_CONTRACT, "objects": rows}
464
+
465
+
466
+ class _DatasetIdentity:
467
+ def __init__(self, payload, overrides=None):
468
+ self.payload = payload
469
+ self.overrides = overrides
470
+ self.calls = 0
471
+
472
+ def source_content(self, *, project_id, clip_id):
473
+ self.calls += 1
474
+ manifest = _source_manifest(self.payload, self.overrides)
475
+ return {
476
+ "contract_version": "x4d-source-content-read-v1",
477
+ "project_id": project_id,
478
+ "clip_id": clip_id,
479
+ "source_revision": self.payload["source_revision"],
480
+ **source_content_revisions(manifest["objects"]),
481
+ "manifest": manifest,
482
+ }
416
483
 
417
484
 
418
485
  class _OneFailedHTTP(_FakeHTTP):
419
- def __init__(self):
420
- super().__init__()
486
+ def __init__(self, overrides=None):
487
+ super().__init__(overrides)
421
488
  self.failed = False
422
489
 
423
490
  def stream(self, method, url):
@@ -441,13 +508,15 @@ class _WorkSessions:
441
508
 
442
509
 
443
510
  class _Client:
444
- def __init__(self, payload):
445
- self._http = _FakeHTTP()
511
+ def __init__(self, payload, overrides=None):
512
+ self._http = _FakeHTTP(overrides)
446
513
  self.clip_work_sessions = _WorkSessions(payload)
514
+ self.dataset_identity = _DatasetIdentity(payload, overrides)
447
515
 
448
516
 
449
- def _local_from_work_session(payload):
517
+ def _local_from_work_session(payload, overrides=None):
450
518
  identity = identity_from_work_session(payload).to_dict()
519
+ identity.update(source_content_revisions(_source_manifest(payload, overrides)["objects"]))
451
520
  return normalize_dataset_local_identity(
452
521
  {
453
522
  key: identity[key]
@@ -457,6 +526,8 @@ def _local_from_work_session(payload):
457
526
  "source_revision",
458
527
  "samples_revision",
459
528
  "sweeps_revision",
529
+ "samples_content_revision",
530
+ "sweeps_content_revision",
460
531
  "calibration_revision",
461
532
  "pose_revision",
462
533
  "annotation_revision",
@@ -527,6 +598,7 @@ def test_policy_upgrade_plan_reuses_keyframes_and_downloads_only_sweeps(tmp_path
527
598
  initial_client = _Client(payload)
528
599
  initial = materialize_work_session_clip(
529
600
  payload,
601
+ source_content=_source_manifest(payload),
530
602
  output_dir=clip_dir,
531
603
  client=initial_client,
532
604
  workers=1,
@@ -618,105 +690,169 @@ def test_policy_upgrade_plan_reuses_keyframes_and_downloads_only_sweeps(tmp_path
618
690
  assert client.clip_work_sessions.calls == 1
619
691
 
620
692
 
621
- def test_samples_change_refreshes_only_the_samples_group(tmp_path):
622
- initial = _work_session_with_sweep()
693
+ def _materialized_full(tmp_path, payload):
623
694
  clip_dir = tmp_path / "clips" / "clip-a"
624
- materialize_work_session_clip(
625
- initial,
695
+ result = materialize_work_session_clip(
696
+ payload,
697
+ source_content=_source_manifest(payload),
626
698
  output_dir=clip_dir,
627
- client=_Client(initial),
699
+ client=_Client(payload),
628
700
  workers=1,
629
701
  materialization_policy="full",
630
702
  )
703
+ assert result["errors"] == []
704
+ return clip_dir
705
+
706
+
707
+ def _group_paths(payload, group):
708
+ return sorted(
709
+ row["file_path"]
710
+ for row in payload["sample_data"]
711
+ if row["file_path"].startswith(group + "/")
712
+ )
713
+
714
+
715
+ def _sync_one(tmp_path, *, initial, target_payload, overrides, changes):
631
716
  predecessor = _local_from_work_session(initial)
717
+ target = _local_from_work_session(target_payload, overrides)
718
+ plan = build_update_plan(
719
+ _response(
720
+ [_record(predecessor=predecessor, target=target, changes=changes)]
721
+ ),
722
+ materialization_policy="full",
723
+ local_materializations=_local_materializations(predecessor),
724
+ )
725
+ client = _Client(target_payload, overrides)
726
+ return plan, client, sync_update_plan(
727
+ client, plan=plan, data_root=tmp_path, workers=1
728
+ )
729
+
730
+
731
+ def test_metadata_only_samples_change_transfers_no_bytes(tmp_path):
732
+ initial = _work_session_with_sweep()
733
+ clip_dir = _materialized_full(tmp_path, initial)
632
734
  target_payload = copy.deepcopy(initial)
633
735
  target_payload["source_revision"] = "source-samples-v2"
634
736
  target_payload["samples_revision"] = "sha256:" + "4" * 64
635
- target = _local_from_work_session(target_payload)
636
- record = _record(
637
- predecessor=predecessor,
638
- target=target,
737
+
738
+ plan, client, result = _sync_one(
739
+ tmp_path,
740
+ initial=initial,
741
+ target_payload=target_payload,
742
+ overrides=None,
639
743
  changes=("source_revision_changed", "samples_changed"),
640
744
  )
641
- plan = build_update_plan(
642
- _response([record]),
643
- materialization_policy="full",
644
- local_materializations=_local_materializations(predecessor),
645
- )
745
+
646
746
  assert plan["actions"]["refresh_samples"] == ["clip-a"]
647
- assert plan["actions"]["refresh_sweeps"] == []
747
+ assert result["failures"] == []
748
+ assert client._http.urls == []
749
+ assert result["source_transfer"]["downloaded_objects"] == 0
750
+ assert result["source_transfer"]["downloaded_bytes"] == 0
751
+ reused = result["executed_updates"][0]["result"]["reused_assets"]
752
+ assert {item["reuse"] for item in reused} == {"in_place"}
753
+ assert len(reused) == len(_group_paths(initial, "samples"))
754
+ cache_identity = load_materialized_cache_identity(clip_dir)
755
+ assert cache_identity["samples_revision"] == target_payload["samples_revision"]
756
+ assert validate_clip(clip_dir).ok
648
757
 
649
- client = _Client(target_payload)
650
- result = sync_update_plan(
651
- client,
652
- plan=plan,
653
- data_root=tmp_path,
654
- workers=1,
758
+
759
+ def test_samples_content_change_downloads_only_changed_objects(tmp_path):
760
+ initial = _work_session_with_sweep()
761
+ clip_dir = _materialized_full(tmp_path, initial)
762
+ changed = next(p for p in _group_paths(initial, "samples") if p.endswith(".bin"))
763
+ overrides = {changed: _bin_bytes(0.99)}
764
+ target_payload = copy.deepcopy(initial)
765
+ target_payload["source_revision"] = "source-samples-v3"
766
+ target_payload["samples_revision"] = "sha256:" + "4" * 64
767
+
768
+ plan, client, result = _sync_one(
769
+ tmp_path,
770
+ initial=initial,
771
+ target_payload=target_payload,
772
+ overrides=overrides,
773
+ changes=(
774
+ "source_revision_changed",
775
+ "samples_changed",
776
+ "samples_content_changed",
777
+ ),
655
778
  )
656
779
 
657
780
  assert result["failures"] == []
658
- assert client._http.urls
659
- assert all("/samples/" in url for _, url in client._http.urls)
781
+ assert [_url_path(url) for _, url in client._http.urls] == [changed]
660
782
  transfer = result["source_transfer"]
661
- assert transfer["accounting_complete"] is True
662
- assert transfer["planned_objects"] == transfer["downloaded_objects"] == 2
663
- assert transfer["planned_bytes"] == transfer["downloaded_bytes"] > 0
664
- assert {
665
- item["component"]
666
- for item in result["executed_updates"][0]["result"]["downloaded_assets"]
667
- } == {"samples"}
668
- cache_identity = load_materialized_cache_identity(clip_dir)
669
- assert cache_identity["samples_revision"] == target_payload["samples_revision"]
670
- assert cache_identity["sweeps_revision"] == initial["sweeps_revision"]
783
+ assert transfer["planned_objects"] == transfer["downloaded_objects"] == 1
784
+ assert transfer["downloaded_bytes"] == len(overrides[changed])
785
+ assert (clip_dir / changed).read_bytes() == overrides[changed]
786
+ held = load_local_source_content(clip_dir)
787
+ assert held[changed]["sha256"] == (
788
+ "sha256:" + hashlib.sha256(overrides[changed]).hexdigest()
789
+ )
790
+ assert load_materialized_cache_identity(clip_dir)["samples_content_revision"] == (
791
+ _local_from_work_session(target_payload, overrides)["samples_content_revision"]
792
+ )
671
793
 
672
794
 
673
- def test_sweeps_change_refreshes_only_the_sweeps_group(tmp_path):
795
+ def test_renamed_object_is_relocated_without_transfer(tmp_path):
674
796
  initial = _work_session_with_sweep()
675
- clip_dir = tmp_path / "clips" / "clip-a"
676
- materialize_work_session_clip(
677
- initial,
678
- output_dir=clip_dir,
679
- client=_Client(initial),
680
- workers=1,
681
- materialization_policy="full",
797
+ clip_dir = _materialized_full(tmp_path, initial)
798
+ old_path = next(p for p in _group_paths(initial, "samples") if p.endswith(".bin"))
799
+ target_payload = copy.deepcopy(initial)
800
+ target_payload["source_revision"] = "source-renamed"
801
+ target_payload["samples_revision"] = "sha256:" + "9" * 64
802
+ for row in target_payload["sample_data"]:
803
+ if row["file_path"] == old_path:
804
+ # A timestamp correction re-places identical bytes at a new path.
805
+ row["timestamp"]["nanosec"] += 1
806
+ stamp = row["timestamp"]["sec"] * 1_000_000_000 + row["timestamp"]["nanosec"]
807
+ row["file_path"] = f"samples/{row['channel']}/{stamp}.bin"
808
+ new_path = row["file_path"]
809
+ target_payload["asset_urls"] = {
810
+ row["sample_data_token"]: f"/api/v1/files/{row['file_path']}"
811
+ for row in target_payload["sample_data"]
812
+ }
813
+ overrides = {new_path: _asset_bytes(old_path)}
814
+
815
+ plan, client, result = _sync_one(
816
+ tmp_path,
817
+ initial=initial,
818
+ target_payload=target_payload,
819
+ overrides=overrides,
820
+ changes=("source_revision_changed", "samples_changed"),
682
821
  )
683
- predecessor = _local_from_work_session(initial)
822
+
823
+ assert result["failures"] == []
824
+ assert client._http.urls == []
825
+ assert not (clip_dir / old_path).exists()
826
+ assert (clip_dir / new_path).read_bytes() == _asset_bytes(old_path)
827
+ reused = result["executed_updates"][0]["result"]["reused_assets"]
828
+ assert [item["from"] for item in reused if item["reuse"] == "relocated"] == [old_path]
829
+
830
+
831
+ def test_sweeps_content_change_downloads_only_changed_sweeps(tmp_path):
832
+ initial = _work_session_with_sweep()
833
+ clip_dir = _materialized_full(tmp_path, initial)
834
+ changed = _group_paths(initial, "sweeps")[0]
835
+ overrides = {changed: _bin_bytes(0.42) if changed.endswith(".bin") else b"jpeg-v2"}
684
836
  target_payload = copy.deepcopy(initial)
685
837
  target_payload["source_revision"] = "source-sweeps-v2"
686
838
  target_payload["sweeps_revision"] = "sha256:" + "5" * 64
687
- target = _local_from_work_session(target_payload)
688
- record = _record(
689
- predecessor=predecessor,
690
- target=target,
691
- changes=("source_revision_changed", "sweeps_changed"),
692
- )
693
- plan = build_update_plan(
694
- _response([record]),
695
- materialization_policy="full",
696
- local_materializations=_local_materializations(predecessor),
697
- )
698
- assert plan["actions"]["refresh_samples"] == []
699
- assert plan["actions"]["refresh_sweeps"] == ["clip-a"]
700
839
 
701
- client = _Client(target_payload)
702
- result = sync_update_plan(
703
- client,
704
- plan=plan,
705
- data_root=tmp_path,
706
- workers=1,
840
+ plan, client, result = _sync_one(
841
+ tmp_path,
842
+ initial=initial,
843
+ target_payload=target_payload,
844
+ overrides=overrides,
845
+ changes=(
846
+ "source_revision_changed",
847
+ "sweeps_changed",
848
+ "sweeps_content_changed",
849
+ ),
707
850
  )
708
851
 
852
+ assert plan["actions"]["refresh_samples"] == []
853
+ assert plan["actions"]["refresh_sweeps"] == ["clip-a"]
709
854
  assert result["failures"] == []
710
- assert client._http.urls
711
- assert all("/sweeps/" in url for _, url in client._http.urls)
712
- transfer = result["source_transfer"]
713
- assert transfer["accounting_complete"] is True
714
- assert transfer["planned_objects"] == transfer["downloaded_objects"] == 2
715
- assert transfer["planned_bytes"] == transfer["downloaded_bytes"] > 0
716
- assert {
717
- item["component"]
718
- for item in result["executed_updates"][0]["result"]["downloaded_assets"]
719
- } == {"sweeps"}
855
+ assert [_url_path(url) for _, url in client._http.urls] == [changed]
720
856
  cache_identity = load_materialized_cache_identity(clip_dir)
721
857
  assert cache_identity["samples_revision"] == initial["samples_revision"]
722
858
  assert cache_identity["sweeps_revision"] == target_payload["sweeps_revision"]
@@ -724,55 +860,63 @@ def test_sweeps_change_refreshes_only_the_sweeps_group(tmp_path):
724
860
 
725
861
  def test_failed_source_refresh_retains_observed_transfer_accounting(tmp_path):
726
862
  initial = _work_session_with_sweep()
727
- clip_dir = tmp_path / "clips" / "clip-a"
728
- materialize_work_session_clip(
729
- initial,
730
- output_dir=clip_dir,
731
- client=_Client(initial),
732
- workers=1,
733
- materialization_policy="full",
863
+ _materialized_full(tmp_path, initial)
864
+ changed = _group_paths(initial, "samples")
865
+ overrides = {path: b"changed:" + path.encode() for path in changed}
866
+ overrides.update(
867
+ {path: _bin_bytes(0.77) for path in changed if path.endswith(".bin")}
734
868
  )
735
- predecessor = _local_from_work_session(initial)
736
869
  target_payload = copy.deepcopy(initial)
737
870
  target_payload["source_revision"] = "source-samples-failed"
738
871
  target_payload["samples_revision"] = "sha256:" + "6" * 64
739
- target = _local_from_work_session(target_payload)
872
+ predecessor = _local_from_work_session(initial)
873
+ target = _local_from_work_session(target_payload, overrides)
740
874
  plan = build_update_plan(
741
875
  _response(
742
876
  [
743
877
  _record(
744
878
  predecessor=predecessor,
745
879
  target=target,
746
- changes=("source_revision_changed", "samples_changed"),
880
+ changes=(
881
+ "source_revision_changed",
882
+ "samples_changed",
883
+ "samples_content_changed",
884
+ ),
747
885
  )
748
886
  ]
749
887
  ),
750
888
  materialization_policy="full",
751
889
  local_materializations=_local_materializations(predecessor),
752
890
  )
753
- client = _Client(target_payload)
754
- client._http = _OneFailedHTTP()
891
+ client = _Client(target_payload, overrides)
892
+ client._http = _OneFailedHTTP(overrides)
755
893
 
756
- result = sync_update_plan(
757
- client,
758
- plan=plan,
759
- data_root=tmp_path,
760
- workers=1,
761
- )
894
+ result = sync_update_plan(client, plan=plan, data_root=tmp_path, workers=1)
762
895
 
763
896
  assert result["executed_updates"] == []
764
897
  assert result["failures"][0]["code"] == "component_refresh_failed"
765
- assert result["source_transfer"] == {
766
- "planned_objects": 2,
767
- "planned_bytes": 24,
768
- "downloaded_objects": 1,
769
- "downloaded_bytes": 24,
770
- "accounting_complete": False,
771
- }
772
- assert (
773
- result["failures"][0]["details"]["source_transfer"]
774
- == result["source_transfer"]
898
+ transfer = result["source_transfer"]
899
+ assert transfer["planned_objects"] == len(changed)
900
+ assert transfer["downloaded_objects"] == len(changed) - 1
901
+ assert transfer["accounting_complete"] is False
902
+ assert result["failures"][0]["details"]["source_transfer"] == transfer
903
+
904
+
905
+ def test_download_is_verified_against_content_identity(tmp_path):
906
+ initial = current_work_session_payload()
907
+ client = _Client(initial)
908
+ manifest = _source_manifest(initial)
909
+ manifest["objects"][0]["sha256"] = "sha256:" + "0" * 64
910
+ result = materialize_work_session_clip(
911
+ initial,
912
+ source_content=manifest,
913
+ output_dir=tmp_path / "clip",
914
+ client=client,
915
+ workers=1,
775
916
  )
917
+ assert [item["code"] for item in result["errors"]] == ["asset_download_failed"]
918
+ assert "content identity" in result["errors"][0]["message"]
919
+ assert load_materialized_cache_identity(tmp_path / "clip") is None
776
920
 
777
921
 
778
922
  def test_keyframes_cache_records_sweeps_change_without_asset_io(tmp_path):
@@ -780,6 +924,7 @@ def test_keyframes_cache_records_sweeps_change_without_asset_io(tmp_path):
780
924
  clip_dir = tmp_path / "clips" / "clip-a"
781
925
  materialize_work_session_clip(
782
926
  initial,
927
+ source_content=_source_manifest(initial),
783
928
  output_dir=clip_dir,
784
929
  client=_Client(initial),
785
930
  workers=1,
@@ -835,6 +980,7 @@ def test_sync_composes_schema_and_annotation_into_one_clip_update(tmp_path):
835
980
  clip_dir = tmp_path / "clips" / "clip-a"
836
981
  materialize_work_session_clip(
837
982
  initial,
983
+ source_content=_source_manifest(initial),
838
984
  output_dir=clip_dir,
839
985
  client=initial_client,
840
986
  workers=1,
@@ -858,7 +1004,7 @@ def test_sync_composes_schema_and_annotation_into_one_clip_update(tmp_path):
858
1004
  result = sync_update_plan(client, plan=plan, data_root=tmp_path, workers=1)
859
1005
 
860
1006
  assert result["schema_version"] == SYNC_RESULT_SCHEMA_VERSION
861
- assert result["schema_version"] == "x4d-sync-result-v9"
1007
+ assert result["schema_version"] == "x4d-sync-result-v10"
862
1008
  assert result["source_transfer"] == {
863
1009
  "planned_objects": 0,
864
1010
  "planned_bytes": 0,