x4d-devkit 0.43.0rc4__tar.gz → 0.45.0rc1__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 (134) hide show
  1. {x4d_devkit-0.43.0rc4/x4d_devkit.egg-info → x4d_devkit-0.45.0rc1}/PKG-INFO +10 -10
  2. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/README.md +9 -9
  3. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/pyproject.toml +1 -1
  4. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_boundary.py +5 -0
  5. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_contract_validation.py +3 -4
  6. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_current_state.py +4 -2
  7. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_dataset_identity.py +5 -2
  8. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_export_bundle.py +2 -2
  9. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_inference_input.py +2 -2
  10. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_inference_schemas.py +4 -3
  11. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_inference_service.py +1 -1
  12. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_label_schemas.py +4 -2
  13. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_loader.py +1 -1
  14. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_models.py +2 -3
  15. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_preannotation_client.py +1 -1
  16. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_sdk_api.py +4 -4
  17. x4d_devkit-0.45.0rc1/tests/test_source_attributes.py +54 -0
  18. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_training_identity.py +3 -3
  19. x4d_devkit-0.45.0rc1/tests/test_vector_map.py +119 -0
  20. x4d_devkit-0.45.0rc1/tests/test_vector_map_geometry.py +76 -0
  21. x4d_devkit-0.45.0rc1/tests/test_vector_map_session.py +147 -0
  22. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_work_session.py +11 -6
  23. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/__init__.py +12 -0
  24. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/annotation_contract.py +2 -2
  25. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/cli.py +1 -1
  26. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/clip_work_sessions.py +12 -0
  27. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/core/loader.py +18 -5
  28. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/core/models.py +6 -6
  29. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/current_state.py +15 -8
  30. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/dataset_contract.py +16 -8
  31. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/dataset_identity.py +14 -8
  32. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/eval/tracking.py +4 -4
  33. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/export_bundle.py +1 -1
  34. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/inference/input.py +1 -1
  35. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/inference/schemas.py +2 -2
  36. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/label_schema.py +54 -1
  37. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/lidar_materialization.py +1 -1
  38. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/materialize.py +32 -6
  39. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/semantic_scene_contract.py +3 -3
  40. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/time_contract.py +1 -1
  41. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/validation/platform_pose.py +1 -1
  42. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/validation/validator.py +14 -7
  43. x4d_devkit-0.45.0rc1/x4d_devkit/vector_map.py +371 -0
  44. x4d_devkit-0.45.0rc1/x4d_devkit/vector_map_geometry.py +221 -0
  45. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/work_session.py +21 -1
  46. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1/x4d_devkit.egg-info}/PKG-INFO +10 -10
  47. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit.egg-info/SOURCES.txt +6 -0
  48. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/LICENSE +0 -0
  49. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/setup.cfg +0 -0
  50. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_annotation_contract.py +0 -0
  51. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_annotation_reference.py +0 -0
  52. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_assets_client.py +0 -0
  53. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_box.py +0 -0
  54. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_build_info.py +0 -0
  55. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_clean_basemap.py +0 -0
  56. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_clean_basemap_client.py +0 -0
  57. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_clean_basemap_preannotation.py +0 -0
  58. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_cli_auth.py +0 -0
  59. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_client_json_contract.py +0 -0
  60. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_client_projects.py +0 -0
  61. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_clip_archives.py +0 -0
  62. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_coordinate_frames.py +0 -0
  63. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_current_state_identity_consumers_example.py +0 -0
  64. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_dataset_contract.py +0 -0
  65. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_detection_eval.py +0 -0
  66. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_evaluate.py +0 -0
  67. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_image_review.py +0 -0
  68. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_lidar_materialization.py +0 -0
  69. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_matching.py +0 -0
  70. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_metrics.py +0 -0
  71. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_platform_pose.py +0 -0
  72. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_reconstruction_client.py +0 -0
  73. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_release_gate.py +0 -0
  74. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_roadmark_preview.py +0 -0
  75. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_semantic_scene_authoring.py +0 -0
  76. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_semantic_scene_client.py +0 -0
  77. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_semantic_scene_contract.py +0 -0
  78. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_semantic_scene_native_clip.py +0 -0
  79. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_time_contract.py +0 -0
  80. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_token.py +0 -0
  81. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_tracking_eval.py +0 -0
  82. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_transform.py +0 -0
  83. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_transform_chain.py +0 -0
  84. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/tests/test_validation.py +0 -0
  85. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/annotation_reference.py +0 -0
  86. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/build_info.py +0 -0
  87. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/clean_basemap.py +0 -0
  88. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/__init__.py +0 -0
  89. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/_origin.py +0 -0
  90. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/annotation_reference.py +0 -0
  91. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/assets.py +0 -0
  92. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/bags.py +0 -0
  93. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/capabilities.py +0 -0
  94. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/checkpoints.py +0 -0
  95. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/clean_basemap.py +0 -0
  96. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/client.py +0 -0
  97. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/clips.py +0 -0
  98. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/dataset_identity.py +0 -0
  99. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/image_review.py +0 -0
  100. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/label_schemas.py +0 -0
  101. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/preannotation.py +0 -0
  102. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/projects.py +0 -0
  103. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/reconstruction.py +0 -0
  104. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/semantic_scene.py +0 -0
  105. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/semantic_scene_authoring.py +0 -0
  106. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/services.py +0 -0
  107. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/client/test_sets.py +0 -0
  108. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/consumer_identity.py +0 -0
  109. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/coordinate_contract.py +0 -0
  110. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/core/__init__.py +0 -0
  111. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/core/token.py +0 -0
  112. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/core/transform.py +0 -0
  113. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/eval/__init__.py +0 -0
  114. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/eval/api.py +0 -0
  115. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/eval/box.py +0 -0
  116. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/eval/detection.py +0 -0
  117. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/eval/matching.py +0 -0
  118. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/eval/metrics.py +0 -0
  119. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/image_review.py +0 -0
  120. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/inference/__init__.py +0 -0
  121. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/inference/models.py +0 -0
  122. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/inference/service.py +0 -0
  123. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/inference/tasks.py +0 -0
  124. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/json_contract.py +0 -0
  125. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/roadmark_preview.py +0 -0
  126. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/semantic_scene_authoring.py +0 -0
  127. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/training_identity.py +0 -0
  128. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/validation/__init__.py +0 -0
  129. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/validation/contract.py +0 -0
  130. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit/validation/report.py +0 -0
  131. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit.egg-info/dependency_links.txt +0 -0
  132. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit.egg-info/entry_points.txt +0 -0
  133. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/x4d_devkit.egg-info/requires.txt +0 -0
  134. {x4d_devkit-0.43.0rc4 → x4d_devkit-0.45.0rc1}/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.43.0rc4
3
+ Version: 0.45.0rc1
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
@@ -97,18 +97,18 @@ platform-to-platform data flow. See
97
97
  generic identity model, and
98
98
  [docs/training-current-state-data.md](docs/training-current-state-data.md) for
99
99
  work-session materialization and conditional annotation writeback. Every
100
- Dataset 0.17 Clip carries its authoritative Label Schema snapshot at root
100
+ Dataset 0.18 Clip carries its authoritative Label Schema snapshot at root
101
101
  `label_schema.json`.
102
102
  Shared validation rules for platform submit, X-Points preflight, local clip
103
103
  validation, and training consumers are documented in
104
104
  [docs/shared-contract-validation.md](docs/shared-contract-validation.md).
105
- The Dataset 0.17 native contract, exact nanosecond time, Multi-LiDAR layout,
105
+ The Dataset 0.18 native contract, exact nanosecond time, Multi-LiDAR layout,
106
106
  and model materialization are documented in
107
- [docs/dataset-0.17.md](docs/dataset-0.17.md). The single-file annotation
107
+ [docs/dataset-0.18.md](docs/dataset-0.18.md). The single-file annotation
108
108
  authority, semantic revision, and
109
109
  derived track views are documented in
110
110
  [docs/annotation-contract.md](docs/annotation-contract.md).
111
- Portable export producers and consumers share the exact `0.17` manifest
111
+ Portable export producers and consumers share the exact `0.18` manifest
112
112
  parser documented in
113
113
  [docs/export-bundle-contract.md](docs/export-bundle-contract.md).
114
114
  `examples/current_state_identity_consumers.py` provides an offline downstream
@@ -133,7 +133,7 @@ Included:
133
133
  - Typed point-level semantic-scene authoring sessions, dirty semantic-chunk upload, and
134
134
  complete compare-and-set submit without a per-point mutation API.
135
135
  - Strict validation of `x4d-training-identity-v5` manifests.
136
- - Strict producer-option and manifest parsing for export bundle `0.17`.
136
+ - Strict producer-option and manifest parsing for export bundle `0.18`.
137
137
  - Clip preannotation protocol, source-only inference adapter, and service
138
138
  wrapper for algorithm projects.
139
139
 
@@ -247,7 +247,7 @@ comparison = compare_project_label_schema_snapshots(project_schema, current)
247
247
  assert comparison.status == "same"
248
248
  ```
249
249
 
250
- The native Dataset 0.17 Clip stores the immutable identity in
250
+ The native Dataset 0.18 Clip stores the immutable identity in
251
251
  `meta.label_schema` and the complete authoritative snapshot in root
252
252
  `label_schema.json`. Registry records are used when creating/binding Clips, not
253
253
  when loading or validating their current contents.
@@ -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-v6`, and
268
- `x4d-devkit dataset diff` writes `x4d-update-plan-v8`. The v8 plan records the
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
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.
@@ -402,7 +402,7 @@ asset existence and point-cloud layout checks without downloading the assets.
402
402
 
403
403
  Third-party model projects should use `x4d-devkit[inference]` instead of
404
404
  hand-writing the X-4D preannotation protocol. The pipeline receives an
405
- annotation-isolated `InferenceClip` and returns one complete Dataset 0.17
405
+ annotation-isolated `InferenceClip` and returns one complete Dataset 0.18
406
406
  annotation document.
407
407
 
408
408
  ```python
@@ -54,18 +54,18 @@ platform-to-platform data flow. See
54
54
  generic identity model, and
55
55
  [docs/training-current-state-data.md](docs/training-current-state-data.md) for
56
56
  work-session materialization and conditional annotation writeback. Every
57
- Dataset 0.17 Clip carries its authoritative Label Schema snapshot at root
57
+ Dataset 0.18 Clip carries its authoritative Label Schema snapshot at root
58
58
  `label_schema.json`.
59
59
  Shared validation rules for platform submit, X-Points preflight, local clip
60
60
  validation, and training consumers are documented in
61
61
  [docs/shared-contract-validation.md](docs/shared-contract-validation.md).
62
- The Dataset 0.17 native contract, exact nanosecond time, Multi-LiDAR layout,
62
+ The Dataset 0.18 native contract, exact nanosecond time, Multi-LiDAR layout,
63
63
  and model materialization are documented in
64
- [docs/dataset-0.17.md](docs/dataset-0.17.md). The single-file annotation
64
+ [docs/dataset-0.18.md](docs/dataset-0.18.md). The single-file annotation
65
65
  authority, semantic revision, and
66
66
  derived track views are documented in
67
67
  [docs/annotation-contract.md](docs/annotation-contract.md).
68
- Portable export producers and consumers share the exact `0.17` manifest
68
+ Portable export producers and consumers share the exact `0.18` manifest
69
69
  parser documented in
70
70
  [docs/export-bundle-contract.md](docs/export-bundle-contract.md).
71
71
  `examples/current_state_identity_consumers.py` provides an offline downstream
@@ -90,7 +90,7 @@ Included:
90
90
  - Typed point-level semantic-scene authoring sessions, dirty semantic-chunk upload, and
91
91
  complete compare-and-set submit without a per-point mutation API.
92
92
  - Strict validation of `x4d-training-identity-v5` manifests.
93
- - Strict producer-option and manifest parsing for export bundle `0.17`.
93
+ - Strict producer-option and manifest parsing for export bundle `0.18`.
94
94
  - Clip preannotation protocol, source-only inference adapter, and service
95
95
  wrapper for algorithm projects.
96
96
 
@@ -204,7 +204,7 @@ comparison = compare_project_label_schema_snapshots(project_schema, current)
204
204
  assert comparison.status == "same"
205
205
  ```
206
206
 
207
- The native Dataset 0.17 Clip stores the immutable identity in
207
+ The native Dataset 0.18 Clip stores the immutable identity in
208
208
  `meta.label_schema` and the complete authoritative snapshot in root
209
209
  `label_schema.json`. Registry records are used when creating/binding Clips, not
210
210
  when loading or validating their current contents.
@@ -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-v6`, and
225
- `x4d-devkit dataset diff` writes `x4d-update-plan-v8`. The v8 plan records the
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
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.
@@ -359,7 +359,7 @@ asset existence and point-cloud layout checks without downloading the assets.
359
359
 
360
360
  Third-party model projects should use `x4d-devkit[inference]` instead of
361
361
  hand-writing the X-4D preannotation protocol. The pipeline receives an
362
- annotation-isolated `InferenceClip` and returns one complete Dataset 0.17
362
+ annotation-isolated `InferenceClip` and returns one complete Dataset 0.18
363
363
  annotation document.
364
364
 
365
365
  ```python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "x4d-devkit"
7
- version = "0.43.0rc4"
7
+ version = "0.45.0rc1"
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"
@@ -77,6 +77,11 @@ def test_top_level_exports_only_stable_sdk_surface():
77
77
 
78
78
  assert set(x4d_devkit.__all__) == {
79
79
  "__version__",
80
+ "VECTOR_MAP_CONTRACT_VERSION",
81
+ "VectorMapDocument",
82
+ "parse_vector_map",
83
+ "parse_vector_map_change",
84
+ "vector_map_geometry_rules",
80
85
  "ANNOTATION_CONTRACT_VERSION",
81
86
  "CLEAN_BASEMAP_CONTRACT_VERSION",
82
87
  "CleanBasemapManifest",
@@ -24,9 +24,8 @@ def _time(value: int) -> dict[str, int]:
24
24
  def _meta(*, keyframe_count: int = 1) -> dict:
25
25
  return {
26
26
  "clip_id": "clip-a",
27
- "schema_version": "0.17",
27
+ "schema_version": "0.18",
28
28
  "session_id": "session-a",
29
- "vehicle_id": "vehicle-a",
30
29
  "capture_time": _time(1),
31
30
  "duration_s": float(max(0, keyframe_count - 1)),
32
31
  "keyframe_count": keyframe_count,
@@ -152,7 +151,7 @@ def _context(
152
151
  ) -> ClipValidationContext:
153
152
  return ClipValidationContext(
154
153
  clip_id="clip-a",
155
- clip_schema_version="0.17",
154
+ clip_schema_version="0.18",
156
155
  label_schema=_schema(),
157
156
  samples=[_sample()] if samples is None else samples,
158
157
  sample_data=[_sample_data()] if sample_data is None else sample_data,
@@ -423,7 +422,7 @@ def test_context_rejects_non_list_payloads_and_invalid_meta():
423
422
  with pytest.raises(TypeError):
424
423
  ClipValidationContext(
425
424
  clip_id="clip-a",
426
- clip_schema_version="0.17",
425
+ clip_schema_version="0.18",
427
426
  label_schema=_schema(),
428
427
  samples=(),
429
428
  sample_data=[],
@@ -31,9 +31,10 @@ def _identity(**overrides):
31
31
  "pose_revision": "pose",
32
32
  "annotation_revision": current_work_session_payload()["annotation_revision"],
33
33
  "semantic_scene_revision": None,
34
+ "vector_map_revision": None,
34
35
  "label_schema": schema_identity(),
35
36
  "business_metadata_revision": "business-metadata",
36
- "clip_schema_version": "0.17",
37
+ "clip_schema_version": "0.18",
37
38
  "ready": True,
38
39
  "blockers": [],
39
40
  }
@@ -209,6 +210,7 @@ def test_clip_identity_direct_constructor_requires_explicit_pose_revision():
209
210
  label_schema=label_schema,
210
211
  pose_revision=None,
211
212
  semantic_scene_revision=None,
213
+ vector_map_revision=None,
212
214
  )
213
215
  assert identity.to_dict()["pose_revision"] is None
214
216
 
@@ -231,5 +233,5 @@ def test_normalize_clip_identity_rejects_removed_source_granularity(field):
231
233
 
232
234
 
233
235
  def test_normalize_clip_identity_rejects_non_current_clip_schema_version():
234
- with pytest.raises(ValueError, match="clip_schema_version must be '0.17'"):
236
+ with pytest.raises(ValueError, match="clip_schema_version must be '0.18'"):
235
237
  normalize_clip_identity(_identity(clip_schema_version="0.10"))
@@ -46,9 +46,10 @@ def _identity(clip_id: str = "clip-a", **overrides):
46
46
  "pose_revision": "sha256:pose",
47
47
  "annotation_revision": "sha256:annotation",
48
48
  "semantic_scene_revision": None,
49
+ "vector_map_revision": None,
49
50
  "label_schema": schema_identity(),
50
51
  "business_metadata_revision": "sha256:business-metadata",
51
- "clip_schema_version": "0.17",
52
+ "clip_schema_version": "0.18",
52
53
  "ready": True,
53
54
  "blockers": [],
54
55
  }
@@ -289,6 +290,7 @@ def test_update_plan_maps_each_change_to_independent_portable_action():
289
290
  "refresh_pose": ["clip-a"],
290
291
  "refresh_annotation": ["clip-a"],
291
292
  "refresh_semantic_scene": [],
293
+ "refresh_vector_map": [],
292
294
  "refresh_label_schema": ["clip-a"],
293
295
  "refresh_business_metadata": ["clip-a"],
294
296
  "rematerialize_clip": [],
@@ -459,6 +461,7 @@ def _local_from_work_session(payload):
459
461
  "pose_revision",
460
462
  "annotation_revision",
461
463
  "semantic_scene_revision",
464
+ "vector_map_revision",
462
465
  "label_schema",
463
466
  "business_metadata_revision",
464
467
  "clip_schema_version",
@@ -855,7 +858,7 @@ def test_sync_composes_schema_and_annotation_into_one_clip_update(tmp_path):
855
858
  result = sync_update_plan(client, plan=plan, data_root=tmp_path, workers=1)
856
859
 
857
860
  assert result["schema_version"] == SYNC_RESULT_SCHEMA_VERSION
858
- assert result["schema_version"] == "x4d-sync-result-v8"
861
+ assert result["schema_version"] == "x4d-sync-result-v9"
859
862
  assert result["source_transfer"] == {
860
863
  "planned_objects": 0,
861
864
  "planned_bytes": 0,
@@ -20,7 +20,7 @@ def _manifest(*, include_sweeps: bool = True) -> dict:
20
20
  return {
21
21
  "export_id": "exp_20260711_a3f8c1d2",
22
22
  "exported_at": {"sec": 1_783_735_200, "nanosec": 123_456_000},
23
- "schema_version": "0.17",
23
+ "schema_version": "0.18",
24
24
  "options": {
25
25
  "include_sweeps": include_sweeps,
26
26
  "pointcloud_format": "pcd_binary",
@@ -31,7 +31,7 @@ def _manifest(*, include_sweeps: bool = True) -> dict:
31
31
  "clips": [
32
32
  {
33
33
  "clip_id": "20260711_G150-002_003",
34
- "schema_version": "0.17",
34
+ "schema_version": "0.18",
35
35
  "label_schema": schema_identity(),
36
36
  "annotation_frame_id": "base_link",
37
37
  "channels": ["cam_front", "lidar"],
@@ -30,7 +30,7 @@ def _inference_clip(tmp_path) -> tuple[ClipLoader, InferenceClip]:
30
30
  sweeps_revision=_digest("b"),
31
31
  calibration_revision="calibration-1",
32
32
  pose_revision="pose-1",
33
- clip_schema_version="0.17",
33
+ clip_schema_version="0.18",
34
34
  )
35
35
  access = PreannotationClipAccess(
36
36
  project_id=7,
@@ -174,7 +174,7 @@ def test_inference_clip_rejects_a_mismatched_source_identity(tmp_path):
174
174
  sweeps_revision=clip.source_identity.sweeps_revision,
175
175
  calibration_revision=clip.source_identity.calibration_revision,
176
176
  pose_revision=clip.source_identity.pose_revision,
177
- clip_schema_version="0.17",
177
+ clip_schema_version="0.18",
178
178
  )
179
179
 
180
180
  with pytest.raises(ValueError, match="source identity"):
@@ -37,7 +37,7 @@ def _request_payload() -> dict:
37
37
  "sweeps_revision": _digest("f"),
38
38
  "calibration_revision": "calibration-1",
39
39
  "pose_revision": "pose-1",
40
- "clip_schema_version": "0.17",
40
+ "clip_schema_version": "0.18",
41
41
  },
42
42
  "label_schema": schema_identity(),
43
43
  "base_annotation_revision": _digest("a"),
@@ -106,8 +106,9 @@ def test_source_identity_projects_only_pure_inference_dependencies():
106
106
  "pose_revision": "pose-1",
107
107
  "annotation_revision": _digest("a"),
108
108
  "semantic_scene_revision": _digest("d"),
109
+ "vector_map_revision": None,
109
110
  "label_schema": schema_identity(),
110
- "clip_schema_version": "0.17",
111
+ "clip_schema_version": "0.18",
111
112
  "ready": True,
112
113
  "blockers": [],
113
114
  }
@@ -119,7 +120,7 @@ def test_source_identity_projects_only_pure_inference_dependencies():
119
120
  "sweeps_revision": _digest("f"),
120
121
  "calibration_revision": "calibration-1",
121
122
  "pose_revision": "pose-1",
122
- "clip_schema_version": "0.17",
123
+ "clip_schema_version": "0.18",
123
124
  }
124
125
 
125
126
 
@@ -83,7 +83,7 @@ def _fixture(tmp_path, pipeline=None, *, run_inline=True):
83
83
  sweeps_revision=_digest("e"),
84
84
  calibration_revision="calibration-1",
85
85
  pose_revision="pose-1",
86
- clip_schema_version="0.17",
86
+ clip_schema_version="0.18",
87
87
  )
88
88
  selected = pipeline or FakePipeline()
89
89
 
@@ -282,9 +282,10 @@ def test_batch_resolution_deduplicates_by_task_free_binding():
282
282
  "pose_revision": "pose-a",
283
283
  "annotation_revision": "ann-a",
284
284
  "semantic_scene_revision": None,
285
+ "vector_map_revision": None,
285
286
  "business_metadata_revision": "business-a",
286
287
  "label_schema": identity,
287
- "clip_schema_version": "0.17",
288
+ "clip_schema_version": "0.18",
288
289
  "ready": True,
289
290
  "blockers": [],
290
291
  },
@@ -298,9 +299,10 @@ def test_batch_resolution_deduplicates_by_task_free_binding():
298
299
  "pose_revision": "pose-b",
299
300
  "annotation_revision": "ann-b",
300
301
  "semantic_scene_revision": None,
302
+ "vector_map_revision": None,
301
303
  "business_metadata_revision": "business-b",
302
304
  "label_schema": identity,
303
- "clip_schema_version": "0.17",
305
+ "clip_schema_version": "0.18",
304
306
  "ready": True,
305
307
  "blockers": [],
306
308
  },
@@ -28,7 +28,7 @@ def loader(tmp_path):
28
28
 
29
29
  def test_loader_builds_current_typed_models_and_indexes(loader):
30
30
  assert isinstance(loader.meta, ClipMeta)
31
- assert loader.schema_version == "0.17"
31
+ assert loader.schema_version == "0.18"
32
32
  assert loader.annotation_revision.startswith("sha256:")
33
33
  assert all(isinstance(item, Sample) for item in loader.samples)
34
34
  assert all(isinstance(item, Annotation) for item in loader.annotations)
@@ -82,9 +82,8 @@ def test_clip_meta_serializes_exact_current_metadata():
82
82
  sensors = {"cam_front": SensorInfo("camera", "cam_front")}
83
83
  meta = ClipMeta(
84
84
  clip_id="clip-a",
85
- schema_version="0.17",
85
+ schema_version="0.18",
86
86
  session_id="session-a",
87
- vehicle_id="vehicle-a",
88
87
  capture_time=TimeNs(1, 2),
89
88
  duration_s=1.0,
90
89
  num_keyframes=1,
@@ -96,7 +95,7 @@ def test_clip_meta_serializes_exact_current_metadata():
96
95
  scene_tags=["urban"],
97
96
  )
98
97
  wire = meta.to_dict()
99
- assert wire["schema_version"] == "0.17"
98
+ assert wire["schema_version"] == "0.18"
100
99
  assert wire["capture_time"] == {"sec": 1, "nanosec": 2}
101
100
  assert wire["scene_tags"] == ["urban"]
102
101
  assert "route_tag" not in wire
@@ -117,7 +117,7 @@ def _portable_candidate() -> dict:
117
117
  "sweeps_revision": _digest("f"),
118
118
  "calibration_revision": "calibration-1",
119
119
  "pose_revision": "pose-1",
120
- "clip_schema_version": "0.17",
120
+ "clip_schema_version": "0.18",
121
121
  },
122
122
  "label_schema": schema_identity(),
123
123
  "base_annotation_revision": _digest("0"),
@@ -11,14 +11,14 @@ 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.43.0rc4"
14
+ assert x4d_devkit.__version__ == "0.45.0rc1"
15
15
 
16
16
 
17
17
  def test_loader_exposes_and_enforces_only_current_schema(tmp_path):
18
18
  loader = ClipLoader(write_current_clip(tmp_path / "current"))
19
- assert loader.schema_version == "0.17"
20
- loader.require_schema_version("0.17")
21
- loader.require_schema_version({"0.17"})
19
+ assert loader.schema_version == "0.18"
20
+ loader.require_schema_version("0.18")
21
+ loader.require_schema_version({"0.18"})
22
22
  with pytest.raises(SchemaVersionMismatch):
23
23
  loader.require_schema_version("0.10")
24
24
  with pytest.raises(SchemaVersionMismatch):
@@ -0,0 +1,54 @@
1
+ """Vehicle-free metadata and the independent business-metadata boundary."""
2
+
3
+ import copy
4
+
5
+ import pytest
6
+
7
+ from tests.current_clip_fixtures import current_clip_tables, write_current_clip
8
+ from x4d_devkit.core.loader import ClipLoader
9
+ from x4d_devkit.dataset_contract import (
10
+ DatasetContractError,
11
+ parse_clip_meta,
12
+ project_business_metadata,
13
+ project_source_metadata,
14
+ )
15
+
16
+
17
+ def test_vehicle_free_clip_loads_and_roundtrips(tmp_path):
18
+ tables = current_clip_tables()
19
+ clip_dir = write_current_clip(tmp_path / "test-clip")
20
+ clip = ClipLoader(clip_dir)
21
+ assert clip.meta.source_attributes is None
22
+ assert clip.meta.to_dict() == tables["meta.json"]
23
+
24
+
25
+ def test_source_attributes_change_only_business_metadata():
26
+ meta = current_clip_tables()["meta.json"]
27
+ candidate = copy.deepcopy(meta)
28
+ candidate["source_attributes"] = {"vehicle_id": "V01", "vehicle_model": "车型 A"}
29
+ parsed = parse_clip_meta(candidate, include_label_schema=True)
30
+ assert parsed == candidate
31
+ assert project_source_metadata(meta) == project_source_metadata(candidate)
32
+ assert project_business_metadata(meta) == {}
33
+ assert project_business_metadata(candidate) == {
34
+ "source_attributes": candidate["source_attributes"]
35
+ }
36
+
37
+
38
+ @pytest.mark.parametrize("attributes", [{}, [], {"a": None}, {"a": 1}, {"a": " x"}, {"a": ""}, {"Bad key": "x"}])
39
+ def test_source_attributes_reject_inexact_values(attributes):
40
+ meta = current_clip_tables()["meta.json"]
41
+ meta["source_attributes"] = attributes
42
+ with pytest.raises(DatasetContractError):
43
+ parse_clip_meta(meta, include_label_schema=True)
44
+
45
+
46
+ def test_previous_vehicle_field_and_dataset_version_are_rejected():
47
+ meta = current_clip_tables()["meta.json"]
48
+ meta["vehicle_id"] = "V01"
49
+ with pytest.raises(DatasetContractError):
50
+ parse_clip_meta(meta, include_label_schema=True)
51
+ del meta["vehicle_id"]
52
+ meta["schema_version"] = "0.17"
53
+ with pytest.raises(DatasetContractError):
54
+ parse_clip_meta(meta, include_label_schema=True)
@@ -38,7 +38,7 @@ def _clip(**overrides):
38
38
  "annotation_revision": "ann",
39
39
  "semantic_scene_revision": None,
40
40
  "label_schema": schema_identity(),
41
- "clip_schema_version": "0.17",
41
+ "clip_schema_version": "0.18",
42
42
  "materialization_policy": "keyframes_only",
43
43
  }
44
44
  data.update(overrides)
@@ -133,7 +133,7 @@ def test_training_identity_file_rejects_duplicate_json_fields(tmp_path):
133
133
 
134
134
 
135
135
  def test_training_identity_rejects_non_current_clip_schema_version():
136
- with pytest.raises(ValueError, match="clip_schema_version must be '0.17'"):
136
+ with pytest.raises(ValueError, match="clip_schema_version must be '0.18'"):
137
137
  build_training_identity_manifest(
138
138
  projects=_projects(),
139
139
  clips=[_clip(clip_schema_version="0.10")]
@@ -251,7 +251,7 @@ def test_project_training_identities_are_dataset_diff_ready(tmp_path):
251
251
  "annotation_revision": "ann",
252
252
  "semantic_scene_revision": None,
253
253
  "label_schema": schema_identity(),
254
- "clip_schema_version": "0.17",
254
+ "clip_schema_version": "0.18",
255
255
  }]
256
256
 
257
257
 
@@ -0,0 +1,119 @@
1
+ import copy
2
+ import pytest
3
+
4
+ from tests.vector_map_fixtures import document, map_schema, map_schema_body
5
+ from tests.test_vector_map_geometry import square
6
+ from x4d_devkit.label_schema import parse_label_schema_body
7
+ from x4d_devkit.vector_map import parse_vector_map, parse_vector_map_change
8
+
9
+
10
+ def parse(value):
11
+ return parse_vector_map(value, map_schema(), clip_id="clip-a")
12
+
13
+
14
+ def test_shared_boundaries_and_directed_neighbor_survive_canonical_roundtrip():
15
+ value = document()
16
+ parsed = parse(value)
17
+ assert parsed.to_dict() == value
18
+ assert parse(parsed).revision == parsed.revision
19
+ value["features"][0]["geometry"]["coordinates"][0][2] = 100
20
+ assert parsed.to_dict()["features"][0]["geometry"]["coordinates"][0][2] == 2
21
+
22
+
23
+ @pytest.mark.parametrize(
24
+ "change",
25
+ [
26
+ "opposite",
27
+ "swapped",
28
+ "range",
29
+ "dangling",
30
+ "duplicate",
31
+ "foreign_clip",
32
+ "wrong_frame",
33
+ "schema",
34
+ ],
35
+ )
36
+ def test_invalid_native_references_geometry_or_identity_fail_closed(change):
37
+ value = document()
38
+ if change == "opposite":
39
+ value["lane_segments"][0]["right_boundary"]["reverse"] = True
40
+ elif change == "swapped":
41
+ lane = value["lane_segments"][0]
42
+ lane["left_boundary"], lane["right_boundary"] = (
43
+ lane["right_boundary"],
44
+ lane["left_boundary"],
45
+ )
46
+ elif change == "range":
47
+ value["lane_segments"][0]["left_boundary"]["end_s_m"] = 100
48
+ elif change == "dangling":
49
+ value["relations"][0]["to_token"] = "missing"
50
+ elif change == "duplicate":
51
+ value["relations"].append({**value["relations"][0], "token": "another"})
52
+ elif change == "foreign_clip":
53
+ value["clip_id"] = "other"
54
+ elif change == "wrong_frame":
55
+ value["frame_id"] = "world"
56
+ elif change == "schema":
57
+ value["provenance"]["label_schema_fingerprint"] = "sha256:" + "0" * 64
58
+ with pytest.raises(ValueError):
59
+ parse(value)
60
+
61
+
62
+ def test_explicit_successor_cycle_is_legal_without_inferred_reciprocal_edges():
63
+ value = document()
64
+ value["relations"] = [
65
+ {
66
+ "token": "cycle",
67
+ "kind": "lane_successor",
68
+ "from_token": "lane-a",
69
+ "to_token": "lane-a",
70
+ "side": None,
71
+ "source_tokens": [],
72
+ }
73
+ ]
74
+ assert len(parse(value).to_dict()["relations"]) == 1
75
+
76
+
77
+ def test_complete_zero_feature_coverage_is_preserved_and_same_category_overlap_rejected():
78
+ value = document()
79
+ value.update(features=[], lane_segments=[], relations=[])
80
+ region = {
81
+ "token": "inspected",
82
+ "geometry": square(),
83
+ "categories": [{"category_id": "area", "state": "complete"}],
84
+ }
85
+ value["coverage"] = [region]
86
+ assert parse(value).to_dict()["coverage"][0]["categories"][0]["state"] == "complete"
87
+ value["coverage"].append({**copy.deepcopy(region), "token": "duplicate-region"})
88
+ with pytest.raises(ValueError, match="overlaps"):
89
+ parse(value)
90
+ value["coverage"][1]["categories"][0]["category_id"] = "boundary"
91
+ parse(value)
92
+
93
+
94
+ def test_schema_geometry_rules_are_mandatory_complete_and_do_not_supply_defaults():
95
+ body = map_schema_body()
96
+ del body["task_views"][0]["geometry_rules"]
97
+ with pytest.raises(ValueError):
98
+ parse_label_schema_body(body)
99
+ body = map_schema_body()
100
+ body["task_views"][0]["geometry_rules"][0]["geometry_types"] = ["polygon", "point"]
101
+ with pytest.raises(ValueError):
102
+ parse_label_schema_body(body)
103
+ value = document()
104
+ value["features"][0]["category_id"] = "area"
105
+ with pytest.raises(ValueError, match="does not permit"):
106
+ parse(value)
107
+
108
+
109
+ def test_component_retain_replace_and_clear_are_distinct_and_explicit():
110
+ for operation in ("retain", "clear"):
111
+ value = {"operation": operation, "base_revision": None, "document": None}
112
+ assert parse_vector_map_change(value, map_schema(), clip_id="clip-a") == value
113
+ value = {"operation": "replace", "base_revision": None, "document": document()}
114
+ assert (
115
+ parse_vector_map_change(value, map_schema(), clip_id="clip-a")["document"]
116
+ == document()
117
+ )
118
+ with pytest.raises(ValueError):
119
+ parse_vector_map_change({"document": None}, map_schema(), clip_id="clip-a")