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.
- {x4d_devkit-0.53.0rc3/x4d_devkit.egg-info → x4d_devkit-0.55.0}/PKG-INFO +3 -3
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/README.md +2 -2
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/pyproject.toml +1 -1
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_dataset_identity.py +266 -120
- x4d_devkit-0.55.0/tests/test_image_source.py +237 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_label_schemas.py +4 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_sdk_api.py +1 -1
- x4d_devkit-0.55.0/tests/test_source_content.py +100 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_work_session.py +17 -1
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/cli.py +61 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/dataset_identity.py +16 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/dataset_identity.py +135 -13
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_clip.py +34 -0
- x4d_devkit-0.55.0/x4d_devkit/image_source.py +490 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/materialize.py +349 -58
- x4d_devkit-0.55.0/x4d_devkit/source_content.py +282 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/validator.py +9 -2
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0/x4d_devkit.egg-info}/PKG-INFO +3 -3
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit.egg-info/SOURCES.txt +4 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/LICENSE +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/setup.cfg +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_accounts.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_annotation_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_annotation_document_hash.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_annotation_reference.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_assets_client.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_boundary.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_box.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_build_info.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_category_audit.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_clean_basemap.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_clean_basemap_client.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_cli_auth.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_client_json_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_client_projects.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_clip_archives.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_contract_validation.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_coordinate_frames.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_current_state.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_current_state_identity_consumers_example.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_dataset_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_detection_eval.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_evaluate.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_export_bundle.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_clip.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_conditional_rules.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_enum_set.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_export.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_geometry.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_groups.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_preannotation.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_reference.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_review.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_schema_migration.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_schema_registry_client.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_schema_upgrade.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_image_work_session.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_inference_input.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_inference_schemas.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_inference_service.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_lidar_materialization.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_loader.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_matching.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_metrics.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_models.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_parse_equivalence.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_platform_pose.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_preannotation_client.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_project_discovery.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_reconstruction_client.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_release_gate.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_roadmark_preview.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_client.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_independent.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_semantic_scene_native_clip.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_source_attributes.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_time_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_token.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_tracking_eval.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_training_identity.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_transform.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_transform_chain.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_validation.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_vector_map.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_vector_map_geometry.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_vector_map_session.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/tests/test_work_session_validation_reuse.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/__init__.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/_image_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/accounts.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/annotation_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/annotation_reference.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/audit.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/build_info.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/clean_basemap.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/__init__.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/_origin.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/accounts.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/annotation_reference.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/assets.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/bags.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/capabilities.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/checkpoints.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/clean_basemap.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/client.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/clip_work_sessions.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/clips.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_clips.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_exports.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_preannotations.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_review.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_schemas.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/image_work_sessions.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/label_schemas.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/preannotation.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/projects.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/reconstruction.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/semantic_scene.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/services.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/client/test_sets.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/consumer_identity.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/coordinate_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/__init__.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/loader.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/models.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/token.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/core/transform.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/current_state.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/dataset_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/__init__.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/api.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/box.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/detection.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/matching.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/metrics.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/eval/tracking.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/export_bundle.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_export.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_geometry.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_groups.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_preannotation.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_reference.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_review.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_schema.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_schema_migration.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_schema_upgrade.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/image_work_session.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/__init__.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/input.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/models.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/schemas.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/service.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/inference/tasks.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/json_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/label_schema.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/lidar_materialization.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/project_discovery.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/roadmark_preview.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/semantic_scene_authoring.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/semantic_scene_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/time_contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/training_identity.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/__init__.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/contract.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/platform_pose.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/validation/report.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/vector_map.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/vector_map_geometry.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit/work_session.py +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit.egg-info/dependency_links.txt +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit.egg-info/entry_points.txt +0 -0
- {x4d_devkit-0.53.0rc3 → x4d_devkit-0.55.0}/x4d_devkit.egg-info/requires.txt +0 -0
- {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.
|
|
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-
|
|
268
|
-
`x4d-devkit dataset diff` writes `x4d-update-plan-
|
|
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-
|
|
225
|
-
`x4d-devkit dataset diff` writes `x4d-update-plan-
|
|
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.
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
],
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
695
|
+
result = materialize_work_session_clip(
|
|
696
|
+
payload,
|
|
697
|
+
source_content=_source_manifest(payload),
|
|
626
698
|
output_dir=clip_dir,
|
|
627
|
-
client=_Client(
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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["
|
|
662
|
-
assert transfer["
|
|
663
|
-
assert
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
|
795
|
+
def test_renamed_object_is_relocated_without_transfer(tmp_path):
|
|
674
796
|
initial = _work_session_with_sweep()
|
|
675
|
-
clip_dir = tmp_path
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
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 =
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
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=(
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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-
|
|
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,
|