devagent-physical-engine 0.12.3__tar.gz → 0.12.4__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 (154) hide show
  1. {devagent_physical_engine-0.12.3/src/devagent_physical_engine.egg-info → devagent_physical_engine-0.12.4}/PKG-INFO +4 -2
  2. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/README.md +3 -1
  3. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/pyproject.toml +1 -1
  4. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/__init__.py +1 -1
  5. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/tool_scene.py +62 -14
  6. devagent_physical_engine-0.12.4/src/devagent_physical_engine/ros2/tool_scene_probe.py +365 -0
  7. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4/src/devagent_physical_engine.egg-info}/PKG-INFO +4 -2
  8. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/SOURCES.txt +1 -0
  9. devagent_physical_engine-0.12.4/tests/test_tool_scene_v0124.py +122 -0
  10. devagent_physical_engine-0.12.3/src/devagent_physical_engine/ros2/tool_scene_probe.py +0 -201
  11. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/NOTICE +0 -0
  12. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/setup.cfg +0 -0
  13. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/__init__.py +0 -0
  14. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/compiler.py +0 -0
  15. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/contracts.py +0 -0
  16. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/coordinator.py +0 -0
  17. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/critic.py +0 -0
  18. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/evidence.py +0 -0
  19. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/interpreter.py +0 -0
  20. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/planner.py +0 -0
  21. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/recovery.py +0 -0
  22. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/routing.py +0 -0
  23. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/runtime.py +0 -0
  24. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/semantic.py +0 -0
  25. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/structured.py +0 -0
  26. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ai_cli.py +0 -0
  27. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/cli.py +0 -0
  28. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/continuous_clearance.py +0 -0
  29. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/doctor.py +0 -0
  30. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/engineering_agent.py +0 -0
  31. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/engineering_request.py +0 -0
  32. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/execution.py +0 -0
  33. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/models.py +0 -0
  34. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/operating_envelope.py +0 -0
  35. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/__init__.py +0 -0
  36. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/benchmark.py +0 -0
  37. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/candidates.py +0 -0
  38. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/contracts.py +0 -0
  39. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/evaluator.py +0 -0
  40. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/evidence.py +0 -0
  41. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/experience.py +0 -0
  42. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/measured.py +0 -0
  43. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/optimizer.py +0 -0
  44. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/orchestrator.py +0 -0
  45. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/physical_campaign.py +0 -0
  46. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/physical_evidence.py +0 -0
  47. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/physical_motion.py +0 -0
  48. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/planning.py +0 -0
  49. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/preexecution_contract.py +0 -0
  50. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/__init__.py +0 -0
  51. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/anthropic.py +0 -0
  52. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/common.py +0 -0
  53. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/factory.py +0 -0
  54. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/gemini.py +0 -0
  55. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/openai.py +0 -0
  56. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_qualification.py +0 -0
  57. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/providers.py +0 -0
  58. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/qualification.py +0 -0
  59. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/qualification_cli.py +0 -0
  60. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/qualification_harness.py +0 -0
  61. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/reference_workcell.py +0 -0
  62. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/robot_platform.py +0 -0
  63. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/robots.py +0 -0
  64. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/__init__.py +0 -0
  65. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/acceptance.py +0 -0
  66. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/commands.py +0 -0
  67. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/continuous_clearance_probe.py +0 -0
  68. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/doctor.py +0 -0
  69. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/fk_pose.py +0 -0
  70. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/fk_probe.py +0 -0
  71. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/frame_alignment.py +0 -0
  72. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/gazebo_cli.py +0 -0
  73. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/gazebo_pose.py +0 -0
  74. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/gazebo_world.py +0 -0
  75. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/joint_state_recorder.py +0 -0
  76. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/measured_motion.py +0 -0
  77. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/moveit_joint_plan_probe.py +0 -0
  78. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/moveit_scene.py +0 -0
  79. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/moveit_task_planner.py +0 -0
  80. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/preexecution.py +0 -0
  81. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/qualification.py +0 -0
  82. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/qualification_v10.py +0 -0
  83. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/qualification_v11.py +0 -0
  84. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/qualification_v12.py +0 -0
  85. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/scene_probe.py +0 -0
  86. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/state_validity_probe.py +0 -0
  87. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/task_adapter.py +0 -0
  88. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/task_visualization.py +0 -0
  89. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/tf_probe.py +0 -0
  90. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/trajectory.py +0 -0
  91. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/ur5e.py +0 -0
  92. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/ur5e_adapter.py +0 -0
  93. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/ur5e_v10_adapter.py +0 -0
  94. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/workpiece_follow.py +0 -0
  95. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/workpiece_gazebo_follower.py +0 -0
  96. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/workpiece_scene.py +0 -0
  97. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/workpiece_scene_probe.py +0 -0
  98. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/setup_profile.py +0 -0
  99. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/sim_real_qualification.py +0 -0
  100. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/simulation.py +0 -0
  101. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/simulation_platform.py +0 -0
  102. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/tool_collision.py +0 -0
  103. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/trajectory_qualification.py +0 -0
  104. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/twin.py +0 -0
  105. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/twin_builder.py +0 -0
  106. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/twin_materialization.py +0 -0
  107. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/verification.py +0 -0
  108. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/workpiece_lifecycle.py +0 -0
  109. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/dependency_links.txt +0 -0
  110. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/entry_points.txt +0 -0
  111. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/requires.txt +0 -0
  112. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/top_level.txt +0 -0
  113. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_agent_core.py +0 -0
  114. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_agent_semantic_hardening.py +0 -0
  115. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ai_cli.py +0 -0
  116. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ai_cli_v07.py +0 -0
  117. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_continuous_clearance_v011.py +0 -0
  118. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_core.py +0 -0
  119. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_engineering_request_v07.py +0 -0
  120. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_gazebo_cli_v0122.py +0 -0
  121. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_interpreter_profiles_v08.py +0 -0
  122. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_live_provider_qualification.py +0 -0
  123. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_measured_motion_v10.py +0 -0
  124. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_moveit_scene_v0123.py +0 -0
  125. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_natural_language_agent_v07.py +0 -0
  126. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_openai_strict_schema_v0113.py +0 -0
  127. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_optimization_v05.py +0 -0
  128. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_physical_campaign_v09.py +0 -0
  129. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_physical_evidence_v09.py +0 -0
  130. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_physical_motion_v09.py +0 -0
  131. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_planning_fallback_v012.py +0 -0
  132. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_preexecution_contract_v10.py +0 -0
  133. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_provider_and_qualification.py +0 -0
  134. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_qualification_cli_lifecycle_v06.py +0 -0
  135. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_qualification_harness_v06.py +0 -0
  136. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_readme_pypi.py +0 -0
  137. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_real_provider_adapters.py +0 -0
  138. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_reference_workcell_v012.py +0 -0
  139. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_robot_platform_v08.py +0 -0
  140. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ros2_production_receipts_v011.py +0 -0
  141. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ros2_simulation.py +0 -0
  142. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ros_trajectory_v09.py +0 -0
  143. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_setup_profile.py +0 -0
  144. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_sim_real_qualification_v011.py +0 -0
  145. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_simulation_platform_v08.py +0 -0
  146. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_tool_collision_v011.py +0 -0
  147. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_trajectory_qualification_v09.py +0 -0
  148. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_twin_materialization_v10.py +0 -0
  149. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_twin_v08.py +0 -0
  150. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ur5e_adapter_scopes_v09.py +0 -0
  151. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ur5e_v10_adapter_scopes.py +0 -0
  152. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_v052_stabilization.py +0 -0
  153. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_v08_catalog_mapping_regression.py +0 -0
  154. {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_workpiece_lifecycle_v011.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devagent-physical-engine
3
- Version: 0.12.3
3
+ Version: 0.12.4
4
4
  Summary: Verification-first agentic engineering, simulation, and evidence runtime for robotics and industrial automation.
5
5
  Author: Tom Ha
6
6
  License: All Rights Reserved
@@ -195,6 +195,8 @@ v0.12.2 additionally hardens Gazebo model-pose read-back across Harmonic CLI ren
195
195
 
196
196
  v0.12.3 hardens MoveIt PlanningScene read-back when MoveIt serializes world collision objects in its own planning/root frame. A different observed frame is never trusted by label alone: DevAgent first proves the observed frame and canonical Twin frame are identity-equivalent through TF, then re-reads the scene and canonicalizes it back to the Twin planning frame before enforcing the exact geometry hash. Non-identity frames and geometry drift remain fail-closed.
197
197
 
198
+ v0.12.4 hardens attached-tool collision read-back against equivalent MoveIt `CollisionObject` pose decompositions. DevAgent now composes the object pose with the primitive pose, canonicalizes equivalent quaternion signs before hashing, and still requires the exact attachment link, object frame, shape, dimensions, operation, and effective geometry. Exact probe evidence is preserved beside the tool payload for target diagnostics; representation-only changes no longer create false mismatches, while physical geometry drift remains fail-closed.
199
+
198
200
  The packaged reference workcell is **not customer-site geometry**. Arbitrary plant layouts require an imported or user-declared physical Twin with trusted poses, collision geometry, tool/TCP information, and workpiece properties. DevAgent does not invent those site facts from a natural-language prompt.
199
201
 
200
202
  The v0.12 implementation remains evidence-gated: source/CI success does not prove the ROS target campaign has passed on a particular workstation. Use the executable qualification below before treating the task-specific path as qualified on that target.
@@ -422,7 +424,7 @@ Detailed engineering documentation lives in the repository:
422
424
 
423
425
  ## Project status
424
426
 
425
- **v0.12.3 — Beta software / evidence-gated task-specific physical simulation.**
427
+ **v0.12.4 — Beta software / evidence-gated task-specific physical simulation.**
426
428
 
427
429
  The software and release pipeline are production-oriented, but physical readiness is scope-specific and evidence-driven. The UR5e task-specific reference-workcell path must pass the executable v0.12 ROS/Gazebo/MoveIt campaign on the target workstation before that scope is considered simulation-qualified there. Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
428
430
 
@@ -154,6 +154,8 @@ v0.12.2 additionally hardens Gazebo model-pose read-back across Harmonic CLI ren
154
154
 
155
155
  v0.12.3 hardens MoveIt PlanningScene read-back when MoveIt serializes world collision objects in its own planning/root frame. A different observed frame is never trusted by label alone: DevAgent first proves the observed frame and canonical Twin frame are identity-equivalent through TF, then re-reads the scene and canonicalizes it back to the Twin planning frame before enforcing the exact geometry hash. Non-identity frames and geometry drift remain fail-closed.
156
156
 
157
+ v0.12.4 hardens attached-tool collision read-back against equivalent MoveIt `CollisionObject` pose decompositions. DevAgent now composes the object pose with the primitive pose, canonicalizes equivalent quaternion signs before hashing, and still requires the exact attachment link, object frame, shape, dimensions, operation, and effective geometry. Exact probe evidence is preserved beside the tool payload for target diagnostics; representation-only changes no longer create false mismatches, while physical geometry drift remains fail-closed.
158
+
157
159
  The packaged reference workcell is **not customer-site geometry**. Arbitrary plant layouts require an imported or user-declared physical Twin with trusted poses, collision geometry, tool/TCP information, and workpiece properties. DevAgent does not invent those site facts from a natural-language prompt.
158
160
 
159
161
  The v0.12 implementation remains evidence-gated: source/CI success does not prove the ROS target campaign has passed on a particular workstation. Use the executable qualification below before treating the task-specific path as qualified on that target.
@@ -381,7 +383,7 @@ Detailed engineering documentation lives in the repository:
381
383
 
382
384
  ## Project status
383
385
 
384
- **v0.12.3 — Beta software / evidence-gated task-specific physical simulation.**
386
+ **v0.12.4 — Beta software / evidence-gated task-specific physical simulation.**
385
387
 
386
388
  The software and release pipeline are production-oriented, but physical readiness is scope-specific and evidence-driven. The UR5e task-specific reference-workcell path must pass the executable v0.12 ROS/Gazebo/MoveIt campaign on the target workstation before that scope is considered simulation-qualified there. Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
387
389
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devagent-physical-engine"
7
- version = "0.12.3"
7
+ version = "0.12.4"
8
8
  description = "Verification-first agentic engineering, simulation, and evidence runtime for robotics and industrial automation."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.11"
@@ -41,4 +41,4 @@ __all__ = [
41
41
  "TwinValidator",
42
42
  ]
43
43
 
44
- __version__ = "0.12.3"
44
+ __version__ = "0.12.4"
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from hashlib import sha256
4
4
  import json
5
+ from math import isfinite, sqrt
5
6
  from pathlib import Path
6
7
 
7
8
  from ..simulation_platform import SimulationPlatformError
@@ -13,6 +14,8 @@ from .commands import RosCommandRunner
13
14
 
14
15
  def _canon_float(value: float) -> float:
15
16
  value = float(value)
17
+ if not isfinite(value):
18
+ raise SimulationPlatformError("tool_collision_geometry_non_finite")
16
19
  return 0.0 if abs(value) < 1e-12 else float(format(value, ".12g"))
17
20
 
18
21
 
@@ -21,6 +24,38 @@ def _hash(payload: object) -> str:
21
24
  return sha256(text.encode("utf-8")).hexdigest()
22
25
 
23
26
 
27
+ def _canonical_quaternion(values) -> tuple[float, float, float, float]:
28
+ quaternion = tuple(float(value) for value in values)
29
+ if len(quaternion) != 4 or any(not isfinite(value) for value in quaternion):
30
+ raise SimulationPlatformError("tool_collision_quaternion_invalid")
31
+ norm = sqrt(sum(value * value for value in quaternion))
32
+ if norm <= 0.0:
33
+ raise SimulationPlatformError("tool_collision_quaternion_invalid")
34
+ quaternion = tuple(value / norm for value in quaternion)
35
+ for component in (quaternion[3], quaternion[0], quaternion[1], quaternion[2]):
36
+ if abs(component) <= 1e-15:
37
+ continue
38
+ if component < 0.0:
39
+ quaternion = tuple(-value for value in quaternion)
40
+ break
41
+ return tuple(_canon_float(value) for value in quaternion)
42
+
43
+
44
+ def _canonical_tool_payload(
45
+ item: dict[str, object], binding: ToolCollisionBinding
46
+ ) -> dict[str, object]:
47
+ return {
48
+ "id": item["id"],
49
+ "link_name": binding.mount_link,
50
+ "geometry_kind": item["geometry_kind"],
51
+ "dimensions_m": [
52
+ _canon_float(value) for value in item["dimensions_m"] # type: ignore[index]
53
+ ],
54
+ "position_m": [_canon_float(value) for value in binding.position_m],
55
+ "quaternion_xyzw": list(_canonical_quaternion(binding.quaternion_xyzw)),
56
+ }
57
+
58
+
24
59
  class MoveItToolCollisionBridge:
25
60
  def __init__(
26
61
  self,
@@ -36,25 +71,19 @@ class MoveItToolCollisionBridge:
36
71
  self.timeout_s = float(timeout_s)
37
72
 
38
73
  @staticmethod
39
- def _geometry_payload(tool: ToolSpec, binding: ToolCollisionBinding) -> tuple[dict[str, object], str]:
74
+ def _geometry_payload(
75
+ tool: ToolSpec, binding: ToolCollisionBinding
76
+ ) -> tuple[dict[str, object], str]:
40
77
  if tool.geometry.kind not in {GeometryKind.BOX, GeometryKind.CYLINDER}:
41
78
  raise SimulationPlatformError("tool_collision_geometry_not_materializable")
42
79
  if not tool.geometry.collision_geometry:
43
80
  raise SimulationPlatformError("tool_collision_geometry_disabled")
44
- item = {
81
+ item: dict[str, object] = {
45
82
  "id": f"devagent_tool_{sha256(tool.tool_id.encode('utf-8')).hexdigest()[:12]}",
46
83
  "geometry_kind": tool.geometry.kind.value,
47
84
  "dimensions_m": [float(value) for value in tool.geometry.dimensions_m],
48
85
  }
49
- canonical = {
50
- "id": item["id"],
51
- "link_name": binding.mount_link,
52
- "geometry_kind": item["geometry_kind"],
53
- "dimensions_m": [_canon_float(value) for value in item["dimensions_m"]],
54
- "position_m": [_canon_float(value) for value in binding.position_m],
55
- "quaternion_xyzw": [_canon_float(value) for value in binding.quaternion_xyzw],
56
- }
57
- return item, _hash(canonical)
86
+ return item, _hash(_canonical_tool_payload(item, binding))
58
87
 
59
88
  def apply_and_verify(
60
89
  self,
@@ -76,10 +105,17 @@ class MoveItToolCollisionBridge:
76
105
  "tool_geometry_hash": geometry_hash,
77
106
  "binding_hash": binding.fingerprint,
78
107
  }
79
- payload_path = self.work_dir / f"{materialization.fingerprint}.{binding.fingerprint}.tool.json"
108
+ payload_path = self.work_dir / (
109
+ f"{materialization.fingerprint}.{binding.fingerprint}.tool.json"
110
+ )
80
111
  temporary = payload_path.with_suffix(payload_path.suffix + ".tmp")
81
112
  temporary.write_text(
82
- json.dumps(payload, sort_keys=True, separators=(",", ":"), allow_nan=False),
113
+ json.dumps(
114
+ payload,
115
+ sort_keys=True,
116
+ separators=(",", ":"),
117
+ allow_nan=False,
118
+ ),
83
119
  encoding="utf-8",
84
120
  )
85
121
  temporary.replace(payload_path)
@@ -101,6 +137,16 @@ class MoveItToolCollisionBridge:
101
137
  raise SimulationPlatformError("tool_scene_probe_output_invalid") from exc
102
138
  if not isinstance(output, dict):
103
139
  raise SimulationPlatformError("tool_scene_probe_output_invalid")
140
+
141
+ # Preserve the exact target read-back evidence even on failure. This keeps
142
+ # target-specific MoveIt serialization visible instead of collapsing it into
143
+ # a generic top-level qualification code.
144
+ output_path = payload_path.with_suffix(".probe.json")
145
+ output_path.write_text(
146
+ json.dumps(output, sort_keys=True, indent=2, allow_nan=False) + "\n",
147
+ encoding="utf-8",
148
+ )
149
+
104
150
  receipt = ToolCollisionReceipt(
105
151
  verified=bool(output.get("success")) and result.ok,
106
152
  code=str(output.get("code") or "tool_scene_probe_failed"),
@@ -121,7 +167,9 @@ class MoveItToolCollisionBridge:
121
167
  ),
122
168
  )
123
169
  if not receipt.verified:
124
- raise SimulationPlatformError(f"tool_collision_verification_failed:{receipt.code}")
170
+ raise SimulationPlatformError(
171
+ f"tool_collision_verification_failed:{receipt.code}"
172
+ )
125
173
  if receipt.observed_link != binding.mount_link:
126
174
  raise SimulationPlatformError("tool_collision_mount_link_mismatch")
127
175
  return receipt
@@ -0,0 +1,365 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from hashlib import sha256
5
+ import json
6
+ from math import isfinite, sqrt
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ def _canon_float(value: float) -> float:
12
+ value = float(value)
13
+ if not isfinite(value):
14
+ raise ValueError("tool_pose_non_finite")
15
+ return 0.0 if abs(value) < 1e-12 else float(format(value, ".12g"))
16
+
17
+
18
+ def _hash(payload: object) -> str:
19
+ text = json.dumps(payload, sort_keys=True, separators=(",", ":"), allow_nan=False)
20
+ return sha256(text.encode("utf-8")).hexdigest()
21
+
22
+
23
+ def _qnorm(values):
24
+ values = tuple(float(value) for value in values)
25
+ if len(values) != 4 or any(not isfinite(value) for value in values):
26
+ raise ValueError("tool_quaternion_invalid")
27
+ norm = sqrt(sum(value * value for value in values))
28
+ if norm <= 0:
29
+ raise ValueError("tool_quaternion_zero")
30
+ return tuple(value / norm for value in values)
31
+
32
+
33
+ def _qcanonical(values):
34
+ """Canonicalize equivalent q/-q rotations to one deterministic representation."""
35
+
36
+ quaternion = _qnorm(values)
37
+ # Prefer positive w. For exact 180-degree rotations where w == 0, use the first
38
+ # non-zero xyz component as the deterministic sign discriminator.
39
+ for component in (quaternion[3], quaternion[0], quaternion[1], quaternion[2]):
40
+ if abs(component) <= 1e-15:
41
+ continue
42
+ if component < 0.0:
43
+ quaternion = tuple(-value for value in quaternion)
44
+ break
45
+ return tuple(_canon_float(value) for value in quaternion)
46
+
47
+
48
+ def _qmul(first, second):
49
+ ax, ay, az, aw = first
50
+ bx, by, bz, bw = second
51
+ return (
52
+ aw * bx + ax * bw + ay * bz - az * by,
53
+ aw * by - ax * bz + ay * bw + az * bx,
54
+ aw * bz + ax * by - ay * bx + az * bw,
55
+ aw * bw - ax * bx - ay * by - az * bz,
56
+ )
57
+
58
+
59
+ def _qconj(quaternion):
60
+ return (-quaternion[0], -quaternion[1], -quaternion[2], quaternion[3])
61
+
62
+
63
+ def _qrot(quaternion, vector):
64
+ quaternion = _qnorm(quaternion)
65
+ output = _qmul(
66
+ _qmul(quaternion, (vector[0], vector[1], vector[2], 0.0)),
67
+ _qconj(quaternion),
68
+ )
69
+ return output[:3]
70
+
71
+
72
+ def _compose(first_position, first_q, second_position, second_q):
73
+ """Compose object pose with a shape pose as defined by CollisionObject.msg."""
74
+
75
+ first_q = _qnorm(first_q)
76
+ second_q = _qnorm(second_q)
77
+ rotated = _qrot(first_q, second_position)
78
+ position = tuple(
79
+ float(first_position[index]) + rotated[index] for index in range(3)
80
+ )
81
+ return position, _qcanonical(_qmul(first_q, second_q))
82
+
83
+
84
+ def _pose_from_msg(pose):
85
+ return (
86
+ (
87
+ float(pose.position.x),
88
+ float(pose.position.y),
89
+ float(pose.position.z),
90
+ ),
91
+ (
92
+ float(pose.orientation.x),
93
+ float(pose.orientation.y),
94
+ float(pose.orientation.z),
95
+ float(pose.orientation.w),
96
+ ),
97
+ )
98
+
99
+
100
+ def _pose_payload(position, quaternion) -> dict[str, list[float]]:
101
+ return {
102
+ "position_m": [_canon_float(value) for value in position],
103
+ "quaternion_xyzw": list(_qcanonical(quaternion)),
104
+ }
105
+
106
+
107
+ def _canonical_tool(
108
+ item: dict[str, Any], *, link_name: str, position, quaternion
109
+ ) -> dict[str, Any]:
110
+ return {
111
+ "id": str(item["id"]),
112
+ "link_name": link_name,
113
+ "geometry_kind": str(item["geometry_kind"]),
114
+ "dimensions_m": [_canon_float(value) for value in item["dimensions_m"]],
115
+ "position_m": [_canon_float(value) for value in position],
116
+ "quaternion_xyzw": list(_qcanonical(quaternion)),
117
+ }
118
+
119
+
120
+ def _primitive(item, SolidPrimitive):
121
+ primitive = SolidPrimitive()
122
+ dimensions = [float(value) for value in item["dimensions_m"]]
123
+ kind = str(item["geometry_kind"])
124
+ if kind == "box":
125
+ primitive.type = SolidPrimitive.BOX
126
+ primitive.dimensions = dimensions
127
+ elif kind == "cylinder":
128
+ primitive.type = SolidPrimitive.CYLINDER
129
+ primitive.dimensions = [dimensions[1], dimensions[0]]
130
+ else:
131
+ raise ValueError(f"unsupported_tool_geometry:{kind}")
132
+ return primitive
133
+
134
+
135
+ def _wait_future(node, future, timeout_s: float) -> bool:
136
+ import rclpy
137
+
138
+ rclpy.spin_until_future_complete(node, future, timeout_sec=timeout_s)
139
+ return future.done()
140
+
141
+
142
+ def apply_verify(path: Path, timeout_s: float) -> dict[str, Any]:
143
+ payload = json.loads(path.read_text(encoding="utf-8"))
144
+ item = dict(payload["tool"])
145
+ link_name = str(payload["mount_link"])
146
+ touch_links = [str(value) for value in payload.get("touch_links", [link_name])]
147
+ position = tuple(float(value) for value in payload["position_m"])
148
+ quaternion = _qcanonical(payload["quaternion_xyzw"])
149
+ expected_hash = str(payload["tool_geometry_hash"])
150
+
151
+ computed_expected_hash = _hash(
152
+ _canonical_tool(
153
+ item,
154
+ link_name=link_name,
155
+ position=position,
156
+ quaternion=quaternion,
157
+ )
158
+ )
159
+ if computed_expected_hash != expected_hash:
160
+ return {
161
+ "success": False,
162
+ "code": "tool_payload_hash_mismatch",
163
+ "expected_attachment_hash": expected_hash,
164
+ "computed_expected_attachment_hash": computed_expected_hash,
165
+ }
166
+
167
+ import rclpy
168
+ from geometry_msgs.msg import Pose
169
+ from moveit_msgs.msg import (
170
+ AttachedCollisionObject,
171
+ CollisionObject,
172
+ PlanningScene,
173
+ PlanningSceneComponents,
174
+ )
175
+ from moveit_msgs.srv import ApplyPlanningScene, GetPlanningScene
176
+ from shape_msgs.msg import SolidPrimitive
177
+
178
+ rclpy.init()
179
+ node = rclpy.create_node("devagent_tool_scene_probe")
180
+ apply_client = node.create_client(ApplyPlanningScene, "/apply_planning_scene")
181
+ get_client = node.create_client(GetPlanningScene, "/get_planning_scene")
182
+ try:
183
+ if not apply_client.wait_for_service(timeout_sec=timeout_s):
184
+ return {"success": False, "code": "apply_planning_scene_unavailable"}
185
+ if not get_client.wait_for_service(timeout_sec=timeout_s):
186
+ return {"success": False, "code": "get_planning_scene_unavailable"}
187
+
188
+ collision = CollisionObject()
189
+ collision.header.frame_id = link_name
190
+ collision.id = str(item["id"])
191
+ collision.operation = CollisionObject.ADD
192
+ collision.pose = Pose()
193
+ collision.pose.position.x = position[0]
194
+ collision.pose.position.y = position[1]
195
+ collision.pose.position.z = position[2]
196
+ collision.pose.orientation.x = quaternion[0]
197
+ collision.pose.orientation.y = quaternion[1]
198
+ collision.pose.orientation.z = quaternion[2]
199
+ collision.pose.orientation.w = quaternion[3]
200
+ identity = Pose()
201
+ identity.orientation.w = 1.0
202
+ collision.primitives = [_primitive(item, SolidPrimitive)]
203
+ collision.primitive_poses = [identity]
204
+
205
+ attached = AttachedCollisionObject()
206
+ attached.link_name = link_name
207
+ attached.touch_links = touch_links
208
+ attached.object = collision
209
+
210
+ scene = PlanningScene()
211
+ scene.is_diff = True
212
+ scene.robot_state.is_diff = True
213
+ scene.robot_state.attached_collision_objects.append(attached)
214
+ request = ApplyPlanningScene.Request()
215
+ request.scene = scene
216
+ future = apply_client.call_async(request)
217
+ if not _wait_future(node, future, timeout_s):
218
+ return {"success": False, "code": "tool_apply_timeout"}
219
+ response = future.result()
220
+ if response is None or not response.success:
221
+ return {"success": False, "code": "tool_apply_rejected"}
222
+
223
+ get_request = GetPlanningScene.Request()
224
+ get_request.components.components = (
225
+ PlanningSceneComponents.ROBOT_STATE_ATTACHED_OBJECTS
226
+ )
227
+ future = get_client.call_async(get_request)
228
+ if not _wait_future(node, future, timeout_s):
229
+ return {"success": False, "code": "tool_readback_timeout"}
230
+ response = future.result()
231
+ if response is None:
232
+ return {"success": False, "code": "tool_readback_empty"}
233
+
234
+ matches = [
235
+ value
236
+ for value in response.scene.robot_state.attached_collision_objects
237
+ if value.object.id == str(item["id"])
238
+ ]
239
+ if len(matches) != 1:
240
+ return {
241
+ "success": False,
242
+ "code": "tool_attachment_readback_count_mismatch",
243
+ "observed_attachment_count": len(matches),
244
+ }
245
+ observed_attachment = matches[0]
246
+ if observed_attachment.link_name != link_name:
247
+ return {
248
+ "success": False,
249
+ "code": "tool_attachment_link_mismatch",
250
+ "observed_link": observed_attachment.link_name,
251
+ "expected_link": link_name,
252
+ }
253
+
254
+ observed = observed_attachment.object
255
+ observed_header_frame = str(observed.header.frame_id)
256
+ if observed_header_frame != link_name:
257
+ return {
258
+ "success": False,
259
+ "code": "tool_attachment_frame_mismatch",
260
+ "observed_link": observed_attachment.link_name,
261
+ "observed_header_frame": observed_header_frame,
262
+ "expected_header_frame": link_name,
263
+ }
264
+ if observed.operation != CollisionObject.ADD:
265
+ return {
266
+ "success": False,
267
+ "code": "tool_attachment_operation_mismatch",
268
+ "observed_operation": int(observed.operation),
269
+ }
270
+ if len(observed.primitives) != 1 or len(observed.primitive_poses) != 1:
271
+ return {
272
+ "success": False,
273
+ "code": "tool_attachment_shape_invalid",
274
+ "observed_primitive_count": len(observed.primitives),
275
+ "observed_primitive_pose_count": len(observed.primitive_poses),
276
+ }
277
+
278
+ primitive = observed.primitives[0]
279
+ if primitive.type == SolidPrimitive.BOX:
280
+ kind = "box"
281
+ dimensions = list(primitive.dimensions)
282
+ elif primitive.type == SolidPrimitive.CYLINDER:
283
+ kind = "cylinder"
284
+ dimensions = [
285
+ primitive.dimensions[SolidPrimitive.CYLINDER_RADIUS],
286
+ primitive.dimensions[SolidPrimitive.CYLINDER_HEIGHT],
287
+ ]
288
+ else:
289
+ return {
290
+ "success": False,
291
+ "code": "tool_attachment_shape_unsupported",
292
+ "observed_shape_type": int(primitive.type),
293
+ }
294
+
295
+ observed_item = {
296
+ "id": observed.id,
297
+ "geometry_kind": kind,
298
+ "dimensions_m": dimensions,
299
+ }
300
+ object_position, object_quaternion = _pose_from_msg(observed.pose)
301
+ primitive_position, primitive_quaternion = _pose_from_msg(
302
+ observed.primitive_poses[0]
303
+ )
304
+ effective_position, effective_quaternion = _compose(
305
+ object_position,
306
+ object_quaternion,
307
+ primitive_position,
308
+ primitive_quaternion,
309
+ )
310
+ observed_hash = _hash(
311
+ _canonical_tool(
312
+ observed_item,
313
+ link_name=link_name,
314
+ position=effective_position,
315
+ quaternion=effective_quaternion,
316
+ )
317
+ )
318
+ success = observed_hash == expected_hash
319
+ return {
320
+ "success": success,
321
+ "code": (
322
+ "tool_collision_verified"
323
+ if success
324
+ else "tool_collision_hash_mismatch"
325
+ ),
326
+ "expected_attachment_hash": expected_hash,
327
+ "observed_attachment_hash": observed_hash,
328
+ "observed_link": observed_attachment.link_name,
329
+ "observed_header_frame": observed_header_frame,
330
+ "observed_object_pose": _pose_payload(
331
+ object_position, object_quaternion
332
+ ),
333
+ "observed_primitive_pose": _pose_payload(
334
+ primitive_position, primitive_quaternion
335
+ ),
336
+ "observed_effective_pose": _pose_payload(
337
+ effective_position, effective_quaternion
338
+ ),
339
+ }
340
+ finally:
341
+ node.destroy_node()
342
+ rclpy.shutdown()
343
+
344
+
345
+ def main() -> int:
346
+ parser = argparse.ArgumentParser()
347
+ parser.add_argument("--input", required=True)
348
+ parser.add_argument("--timeout", type=float, default=20.0)
349
+ args = parser.parse_args()
350
+ try:
351
+ result = apply_verify(
352
+ Path(args.input).expanduser().resolve(), float(args.timeout)
353
+ )
354
+ except Exception as exc:
355
+ result = {
356
+ "success": False,
357
+ "code": f"tool_scene_probe_error:{type(exc).__name__}",
358
+ "detail": str(exc),
359
+ }
360
+ print(json.dumps(result, sort_keys=True))
361
+ return 0 if result.get("success") else 2
362
+
363
+
364
+ if __name__ == "__main__":
365
+ raise SystemExit(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devagent-physical-engine
3
- Version: 0.12.3
3
+ Version: 0.12.4
4
4
  Summary: Verification-first agentic engineering, simulation, and evidence runtime for robotics and industrial automation.
5
5
  Author: Tom Ha
6
6
  License: All Rights Reserved
@@ -195,6 +195,8 @@ v0.12.2 additionally hardens Gazebo model-pose read-back across Harmonic CLI ren
195
195
 
196
196
  v0.12.3 hardens MoveIt PlanningScene read-back when MoveIt serializes world collision objects in its own planning/root frame. A different observed frame is never trusted by label alone: DevAgent first proves the observed frame and canonical Twin frame are identity-equivalent through TF, then re-reads the scene and canonicalizes it back to the Twin planning frame before enforcing the exact geometry hash. Non-identity frames and geometry drift remain fail-closed.
197
197
 
198
+ v0.12.4 hardens attached-tool collision read-back against equivalent MoveIt `CollisionObject` pose decompositions. DevAgent now composes the object pose with the primitive pose, canonicalizes equivalent quaternion signs before hashing, and still requires the exact attachment link, object frame, shape, dimensions, operation, and effective geometry. Exact probe evidence is preserved beside the tool payload for target diagnostics; representation-only changes no longer create false mismatches, while physical geometry drift remains fail-closed.
199
+
198
200
  The packaged reference workcell is **not customer-site geometry**. Arbitrary plant layouts require an imported or user-declared physical Twin with trusted poses, collision geometry, tool/TCP information, and workpiece properties. DevAgent does not invent those site facts from a natural-language prompt.
199
201
 
200
202
  The v0.12 implementation remains evidence-gated: source/CI success does not prove the ROS target campaign has passed on a particular workstation. Use the executable qualification below before treating the task-specific path as qualified on that target.
@@ -422,7 +424,7 @@ Detailed engineering documentation lives in the repository:
422
424
 
423
425
  ## Project status
424
426
 
425
- **v0.12.3 — Beta software / evidence-gated task-specific physical simulation.**
427
+ **v0.12.4 — Beta software / evidence-gated task-specific physical simulation.**
426
428
 
427
429
  The software and release pipeline are production-oriented, but physical readiness is scope-specific and evidence-driven. The UR5e task-specific reference-workcell path must pass the executable v0.12 ROS/Gazebo/MoveIt campaign on the target workstation before that scope is considered simulation-qualified there. Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
428
430
 
@@ -140,6 +140,7 @@ tests/test_setup_profile.py
140
140
  tests/test_sim_real_qualification_v011.py
141
141
  tests/test_simulation_platform_v08.py
142
142
  tests/test_tool_collision_v011.py
143
+ tests/test_tool_scene_v0124.py
143
144
  tests/test_trajectory_qualification_v09.py
144
145
  tests/test_twin_materialization_v10.py
145
146
  tests/test_twin_v08.py