spatialdds 0.8.2__tar.gz → 0.8.3__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 (247) hide show
  1. {spatialdds-0.8.2 → spatialdds-0.8.3}/PKG-INFO +10 -1
  2. {spatialdds-0.8.2 → spatialdds-0.8.3}/README.md +7 -0
  3. {spatialdds-0.8.2 → spatialdds-0.8.3}/pyproject.toml +9 -1
  4. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/__init__.py +2 -2
  5. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/openapi.html +1 -1
  6. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/openapi.yaml +1 -1
  7. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/client.py +69 -7
  8. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/_participant.py +12 -10
  9. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/codec.py +10 -8
  10. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/errors.py +10 -0
  11. spatialdds-0.8.3/spatialdds/mcp_server.py +1159 -0
  12. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/serve.py +25 -5
  13. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/gateway.py +20 -6
  14. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/publish.py +13 -8
  15. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/tenancy.py +3 -1
  16. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/webjson.py +48 -6
  17. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds.egg-info/PKG-INFO +10 -1
  18. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds.egg-info/SOURCES.txt +1 -0
  19. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds.egg-info/entry_points.txt +1 -0
  20. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds.egg-info/requires.txt +3 -0
  21. {spatialdds-0.8.2 → spatialdds-0.8.3}/setup.cfg +0 -0
  22. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/anchors.idl +0 -0
  23. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/argeo.idl +0 -0
  24. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/common.idl +0 -0
  25. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/core.idl +0 -0
  26. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/discovery.idl +0 -0
  27. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/events.idl +0 -0
  28. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/lidar.idl +0 -0
  29. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/mapping.idl +0 -0
  30. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/provisional/owm.idl +0 -0
  31. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/provisional/radio.idl +0 -0
  32. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/provisional/rf_beam.idl +0 -0
  33. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/rad.idl +0 -0
  34. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/semantics.idl +0 -0
  35. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/slam_frontend.idl +0 -0
  36. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/types.idl +0 -0
  37. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/vio.idl +0 -0
  38. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/idl/vision.idl +0 -0
  39. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/registry.json +0 -0
  40. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/builtin_Time.json +0 -0
  41. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_anchors_AnchorDelta.json +0 -0
  42. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_anchors_AnchorEntry.json +0 -0
  43. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSet.json +0 -0
  44. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetRequest.json +0 -0
  45. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_anchors_AnchorSetResponse.json +0 -0
  46. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_argeo_NodeGeo.json +0 -0
  47. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_argeo_QualityRequirements.json +0 -0
  48. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_argeo_VpsRequest.json +0 -0
  49. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_argeo_VpsResponse.json +0 -0
  50. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_common_AssetRef.json +0 -0
  51. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_common_FrameRef.json +0 -0
  52. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_common_KV.json +0 -0
  53. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_common_MetaKV.json +0 -0
  54. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_common_Vec2.json +0 -0
  55. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_Aabb3.json +0 -0
  56. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_BlobChunk.json +0 -0
  57. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_BlobRef.json +0 -0
  58. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_ComponentRef.json +0 -0
  59. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_Edge.json +0 -0
  60. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_EntityBinding.json +0 -0
  61. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_FrameTransform.json +0 -0
  62. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_FramedPose.json +0 -0
  63. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_GeoAnchor.json +0 -0
  64. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_GeoPose.json +0 -0
  65. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_NavSatStatus.json +0 -0
  66. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_Node.json +0 -0
  67. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_PlannedTrajectory.json +0 -0
  68. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_PlannedWaypoint.json +0 -0
  69. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_PoseSE3.json +0 -0
  70. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_SnapshotRequest.json +0 -0
  71. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_SnapshotResponse.json +0 -0
  72. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_TileKey.json +0 -0
  73. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_TileMeta.json +0 -0
  74. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_core_TilePatch.json +0 -0
  75. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_Announce.json +0 -0
  76. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_Capabilities.json +0 -0
  77. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_ContentAnnounce.json +0 -0
  78. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_CoverageElement.json +0 -0
  79. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_CoverageFilter.json +0 -0
  80. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_CoverageHint.json +0 -0
  81. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_CoverageQuery.json +0 -0
  82. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_CoverageResponse.json +0 -0
  83. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_Depart.json +0 -0
  84. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_KV.json +0 -0
  85. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_ProfileSupport.json +0 -0
  86. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_ServiceSummary.json +0 -0
  87. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_TopicMeta.json +0 -0
  88. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_Transform.json +0 -0
  89. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_disco_ValidityWindow.json +0 -0
  90. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_events_CrossingLine.json +0 -0
  91. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_events_SpatialEvent.json +0 -0
  92. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_events_SpatialZone.json +0 -0
  93. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_events_ZoneState.json +0 -0
  94. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_mapping_Edge.json +0 -0
  95. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_mapping_MapAlignment.json +0 -0
  96. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_mapping_MapEvent.json +0 -0
  97. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_mapping_MapMeta.json +0 -0
  98. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_mapping_MapQuality.json +0 -0
  99. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_owm_Entity.json +0 -0
  100. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_owm_ModelCommand.json +0 -0
  101. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_owm_ModelPose.json +0 -0
  102. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_semantics_Detection2D.json +0 -0
  103. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_semantics_Detection2DSet.json +0 -0
  104. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_semantics_Detection3D.json +0 -0
  105. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_semantics_Detection3DSet.json +0 -0
  106. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrack.json +0 -0
  107. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_semantics_FusedTrackSet.json +0 -0
  108. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_semantics_Keypoint3D.json +0 -0
  109. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_semantics_KeypointLink.json +0 -0
  110. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_common_Axis.json +0 -0
  111. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameHeader.json +0 -0
  112. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_common_FrameQuality.json +0 -0
  113. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_common_Linspace.json +0 -0
  114. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_common_ROI.json +0 -0
  115. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIReply.json +0 -0
  116. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_common_ROIRequest.json +0 -0
  117. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_common_StreamMeta.json +0 -0
  118. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetection.json +0 -0
  119. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarDetectionSet.json +0 -0
  120. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarFrame.json +0 -0
  121. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_lidar_LidarMeta.json +0 -0
  122. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetection.json +0 -0
  123. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadDetectionSet.json +0 -0
  124. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadSensorMeta.json +0 -0
  125. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorFrame.json +0 -0
  126. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_rad_RadTensorMeta.json +0 -0
  127. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioObservation.json +0 -0
  128. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioScan.json +0 -0
  129. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_radio_RadioSensorMeta.json +0 -0
  130. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamArraySet.json +0 -0
  131. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamFrame.json +0 -0
  132. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_rf_beam_RfBeamMeta.json +0 -0
  133. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_vision_CamIntrinsics.json +0 -0
  134. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_vision_Keypoint2D.json +0 -0
  135. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_vision_Track2D.json +0 -0
  136. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionDetections.json +0 -0
  137. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionFrame.json +0 -0
  138. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_sensing_vision_VisionMeta.json +0 -0
  139. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_slam_frontend_CameraInfo.json +0 -0
  140. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_slam_frontend_Feature2D.json +0 -0
  141. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_slam_frontend_FeatureMatch.json +0 -0
  142. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_slam_frontend_KeyframeFeatures.json +0 -0
  143. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_slam_frontend_Landmark.json +0 -0
  144. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_slam_frontend_MatchSet.json +0 -0
  145. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_slam_frontend_TrackObs.json +0 -0
  146. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_slam_frontend_Tracklet.json +0 -0
  147. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_vio_FusedState.json +0 -0
  148. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_vio_ImuInfo.json +0 -0
  149. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_vio_ImuSample.json +0 -0
  150. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_vio_MagnetometerSample.json +0 -0
  151. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/schemas/spatial_vio_SensorFusionSample.json +0 -0
  152. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/artifacts/serializer_tables.json +0 -0
  153. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/canonical.py +0 -0
  154. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/canonjson.py +0 -0
  155. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/__init__.py +0 -0
  156. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/_extra.py +0 -0
  157. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/blob.py +0 -0
  158. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/qos.py +0 -0
  159. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/record.py +0 -0
  160. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/registry.py +0 -0
  161. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/__init__.py +0 -0
  162. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/_field_aliases.py +0 -0
  163. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/builtin/__init__.py +0 -0
  164. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/builtin/_types.py +0 -0
  165. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/__init__.py +0 -0
  166. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/anchors/__init__.py +0 -0
  167. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/anchors/_anchors.py +0 -0
  168. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/argeo/__init__.py +0 -0
  169. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/argeo/_argeo.py +0 -0
  170. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/common/__init__.py +0 -0
  171. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/common/_types.py +0 -0
  172. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/core/GnssService/__init__.py +0 -0
  173. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/core/GnssService/_core.py +0 -0
  174. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/core/__init__.py +0 -0
  175. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/core/_core.py +0 -0
  176. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/disco/__init__.py +0 -0
  177. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/disco/_discovery.py +0 -0
  178. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/__init__.py +0 -0
  179. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/_events.py +0 -0
  180. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/__init__.py +0 -0
  181. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/crossing_direction_enum/__init__.py +0 -0
  182. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/crossing_direction_enum/_events.py +0 -0
  183. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/event_state_enum/__init__.py +0 -0
  184. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/event_state_enum/_events.py +0 -0
  185. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/event_type_enum/__init__.py +0 -0
  186. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/event_type_enum/_events.py +0 -0
  187. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/severity_enum/__init__.py +0 -0
  188. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/severity_enum/_events.py +0 -0
  189. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/zone_kind_enum/__init__.py +0 -0
  190. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/events/enums/zone_kind_enum/_events.py +0 -0
  191. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/__init__.py +0 -0
  192. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/_mapping.py +0 -0
  193. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/__init__.py +0 -0
  194. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/alignment_method/__init__.py +0 -0
  195. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/alignment_method/_mapping.py +0 -0
  196. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/edge_type/__init__.py +0 -0
  197. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/edge_type/_mapping.py +0 -0
  198. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/map_event_kind/__init__.py +0 -0
  199. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/map_event_kind/_mapping.py +0 -0
  200. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/map_kind/__init__.py +0 -0
  201. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/map_kind/_mapping.py +0 -0
  202. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/map_status/__init__.py +0 -0
  203. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/mapping/enums/map_status/_mapping.py +0 -0
  204. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/owm/__init__.py +0 -0
  205. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/owm/_owm.py +0 -0
  206. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/semantics/__init__.py +0 -0
  207. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/semantics/_semantics.py +0 -0
  208. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/__init__.py +0 -0
  209. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/common/__init__.py +0 -0
  210. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/common/_common.py +0 -0
  211. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/lidar/__init__.py +0 -0
  212. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/lidar/_lidar.py +0 -0
  213. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/rad/__init__.py +0 -0
  214. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/rad/_rad.py +0 -0
  215. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/radio/__init__.py +0 -0
  216. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/radio/_radio.py +0 -0
  217. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/rf_beam/__init__.py +0 -0
  218. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/rf_beam/_rf_beam.py +0 -0
  219. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/vision/__init__.py +0 -0
  220. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/sensing/vision/_vision.py +0 -0
  221. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/slam_frontend/__init__.py +0 -0
  222. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/slam_frontend/_slam_frontend.py +0 -0
  223. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/vio/__init__.py +0 -0
  224. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/dds/types/spatial/vio/_vio.py +0 -0
  225. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/demo/assets/duck.gltf +0 -0
  226. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/demo/assets/fountain-label.txt +0 -0
  227. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/demo/assets/fountain-marker.gltf +0 -0
  228. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/demo/fountain-anchors.mcap +0 -0
  229. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/demo/manifests/content-fountain-label.json +0 -0
  230. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/demo/manifests/content-fountain-marker.json +0 -0
  231. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/events.py +0 -0
  232. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/frames.py +0 -0
  233. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/journal.py +0 -0
  234. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/recency.py +0 -0
  235. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/records.py +0 -0
  236. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/__init__.py +0 -0
  237. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/bus.py +0 -0
  238. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/commands.py +0 -0
  239. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/gw_http.py +0 -0
  240. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/idlmodel.py +0 -0
  241. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/limits.py +0 -0
  242. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/mcapmeta.py +0 -0
  243. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/pin.py +0 -0
  244. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/server/xcdr2.py +0 -0
  245. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds/tokens.py +0 -0
  246. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds.egg-info/dependency_links.txt +0 -0
  247. {spatialdds-0.8.2 → spatialdds-0.8.3}/spatialdds.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatialdds
3
- Version: 0.8.2
3
+ Version: 0.8.3
4
4
  Summary: SpatialDDS for programs: a client for the web binding (Appendix N), a native DDS participant, and the reference registry behind one command.
5
5
  Author: Open AR Cloud
6
6
  License: Apache-2.0
@@ -25,6 +25,8 @@ Provides-Extra: dds
25
25
  Requires-Dist: cyclonedds<12,>=11.0.1; extra == "dds"
26
26
  Requires-Dist: mcap>=1.1; extra == "dds"
27
27
  Requires-Dist: jsonschema>=4.0; extra == "dds"
28
+ Provides-Extra: mcp
29
+ Requires-Dist: mcp<3,>=2.3; extra == "mcp"
28
30
 
29
31
  # spatialdds
30
32
 
@@ -488,6 +490,13 @@ what such a field digests or in what form (spatialdds-py finding 31). Where a
488
490
  declared value does not parse as a SHA-256 of the asset's bytes, this library
489
491
  raises rather than reporting a check it did not perform.
490
492
 
493
+ One warning, not an error: **`AdvertisedOriginWarning`** (a `UserWarning`),
494
+ once per endpoint, when its search results or resolver metadata advertise
495
+ URLs on another origin than the one you connected to. The library reads
496
+ through your origin either way, so it keeps working; a client that follows
497
+ the advertised URLs reaches the other one, which usually means the endpoint
498
+ was started without `--public-url`. The message names both origins.
499
+
491
500
  ## Integrity by default
492
501
 
493
502
  Latched reads revalidate with the ETag the endpoint gave; `fetch_asset` checks
@@ -460,6 +460,13 @@ what such a field digests or in what form (spatialdds-py finding 31). Where a
460
460
  declared value does not parse as a SHA-256 of the asset's bytes, this library
461
461
  raises rather than reporting a check it did not perform.
462
462
 
463
+ One warning, not an error: **`AdvertisedOriginWarning`** (a `UserWarning`),
464
+ once per endpoint, when its search results or resolver metadata advertise
465
+ URLs on another origin than the one you connected to. The library reads
466
+ through your origin either way, so it keeps working; a client that follows
467
+ the advertised URLs reaches the other one, which usually means the endpoint
468
+ was started without `--public-url`. The message names both origins.
469
+
463
470
  ## Integrity by default
464
471
 
465
472
  Latched reads revalidate with the ETag the endpoint gave; `fetch_asset` checks
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spatialdds"
7
- version = "0.8.2"
7
+ version = "0.8.3"
8
8
  description = "SpatialDDS for programs: a client for the web binding (Appendix N), a native DDS participant, and the reference registry behind one command."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -40,9 +40,17 @@ server = ["aiohttp>=3.8", "mcap>=1.1", "jsonschema>=4.0"]
40
40
  # Without this extra `import spatialdds` is unchanged, and `sd.participant`
41
41
  # raises `sd.MissingExtra` naming it.
42
42
  dds = ["cyclonedds>=11.0.1,<12", "mcap>=1.1", "jsonschema>=4.0"]
43
+ # Being an MCP server over the web binding: `spatialdds-mcp`. The official
44
+ # SDK, and nothing else: the server reads with this package's own client,
45
+ # so it needs no aiohttp, no mcap and no cyclonedds. 2.x because its API is
46
+ # the one written against (1.x's FastMCP is 2.x's MCPServer), which needs
47
+ # Python 3.10 or newer; without this extra the base install is unchanged and
48
+ # `spatialdds-mcp` exits saying which line to run.
49
+ mcp = ["mcp>=2.3,<3"]
43
50
 
44
51
  [project.scripts]
45
52
  spatialdds-serve = "spatialdds.serve:main"
53
+ spatialdds-mcp = "spatialdds.mcp_server:main"
46
54
 
47
55
  [project.urls]
48
56
  Homepage = "https://github.com/OpenArCloud/spatialdds-py"
@@ -26,7 +26,7 @@ __all__ = [
26
26
  "connect", "Endpoint", "Hit", "Topic", "Resolved", "Written",
27
27
  "Sample", "Dispose", "EndOfReplay", "Liveliness",
28
28
  "SpatialDDSError", "Unreachable", "Problem", "Unauthorized", "SurfaceOff",
29
- "IntegrityError", "UnverifiableIntegrity",
29
+ "IntegrityError", "UnverifiableIntegrity", "AdvertisedOriginWarning",
30
30
  "lift", "SPEC_TAG", "SPEC_COMMIT", "APPENDIX_N", "__version__",
31
31
  # The record builders, re-exported so that `sd.circle(...)` reads the way
32
32
  # it does in the documentation. They live in `spatialdds.records`.
@@ -41,7 +41,7 @@ __all__ = [
41
41
  "participant", "MissingExtra", "dds", "events", "frames", "Recency",
42
42
  ]
43
43
 
44
- __version__ = "0.8.2"
44
+ __version__ = "0.8.3"
45
45
  #: The SpatialDDS-spec commit whose Appendix N and generated artifacts this
46
46
  #: package implements. Package versions and specification versions move
47
47
  #: independently; this is the one that says what the wire looks like.
@@ -14,4 +14,4 @@ border-bottom:1px solid var(--line)}p{margin:.35rem 0}.mut,.sum .auth{color:
14
14
  var(--mut)}.auth{font-size:.85rem;margin-left:.4rem}.v{display:inline-block;
15
15
  min-width:4.2rem;font:600 12px ui-monospace,monospace;letter-spacing:.04em;
16
16
  color:var(--acc)}.codes{font:12px ui-monospace,monospace;color:var(--mut)}
17
- .s{margin-right:.5rem}.s4,.s5{color:#b5482c}a{color:var(--acc)}</style></head><body><main><h1>SpatialDDS web binding (spatialdds-py)</h1><p class=mut>Version 0.8.2 · OpenAPI 3.1.0 · generated, never hand-written · <a href='openapi'>openapi.yaml</a></p><p>Generated by <code>tools/gen_openapi.py</code> from the gateway&#x27;s own tables and the published JSON Schemas; never edited by hand. Reads are open; writes take a bearer token scoped to scenes. The WebSocket streams (N.5) are described on each collection path, not modelled. A deployment&#x27;s own keyed types (<code>serve --extension</code>) are publishable on their own collection paths, under the same rules as the entity lane when they carry <code>basis</code>, <code>state</code>, <code>state_reason</code> and <code>stamp</code>; a type that names an <code>asserter</code> has it stamped or verified as the bearer token&#x27;s principal (<code>asserter-not-principal</code>, <code>asserter-ungrounded</code>). Their paths are not listed here, because the deployment chooses them.</p><h2>Operations</h2><section id="search"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/search</code></h3><p class="sum">Services near a geohash (§8.2.3 manifests) <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span></p></section><section id="searchPost"><h3><span class="v post">POST</span> <code>/.well-known/spatialdds/search</code></h3><p class="sum">The same search, as a POST body <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="listSchemas"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/schemas</code></h3><p class="sum">The published JSON Schemas, by type <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span></p></section><section id="getSchema"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/schemas/{type}</code></h3><p class="sum"> <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="bootstrap"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/bootstrap</code></h3><p class="sum">DDS-join material (N.2); optional <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="resolverMetadata"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/resolver</code></h3><p class="sum">§7.5.2 resolver metadata <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="openapi"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/openapi</code></h3><p class="sum">This document <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span></p></section><section id="openapiPage"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/openapi.html</code></h3><p class="sum">This document as a static reference page <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span></p></section><section id="resolve"><h3><span class="v get">GET</span> <code>/spatialdds/resolve</code></h3><p class="sum">Resolve a URI to its manifest (§7.5.2) <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">400</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putManifest"><h3><span class="v put">PUT</span> <code>/spatialdds/resolve</code></h3><p class="sum">Publish a manifest, keyed by its own <code>id</code> <span class="auth">token</span></p><p>The <code>uri</code> parameter is not used; the body&#x27;s <code>id</code> is the key. Its scene segment is the lane. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>).</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span> <span class="s s5">503</span></p></section><section id="withdrawManifest"><h3><span class="v delete">DELETE</span> <code>/spatialdds/resolve</code></h3><p class="sum">Withdraw a manifest; <code>resolve</code> then answers 404 <span class="auth">token</span></p><p>Owner-checked by the URI&#x27;s scene segment. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>).</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="getAsset"><h3><span class="v get">GET</span> <code>/assets/{name}</code></h3><p class="sum">Asset bytes this endpoint hosts (implementation-defined) <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="listAnnounce"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/announce/v1</code></h3><p class="sum">Every spatial.disco.Announce instance in the latched set <span class="auth">open</span></p><p>**The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="commandAnnounce"><h3><span class="v post">POST</span> <code>/{scene}/{stream}/announce/v1</code></h3><p class="sum">An N.6 command addressed through the announce lane <span class="auth">token</span></p><p>Accepted only on a lane whose topic advertises <code>accepts_commands</code>; otherwise <code>405</code>. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch.</p><p class="codes"><span class="s s2">202</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">409</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="getAnnounce"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/announce/v1/{key}</code></h3><p class="sum">One spatial.disco.Announce instance <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putAnnounce"><h3><span class="v put">PUT</span> <code>/{scene}/{stream}/announce/v1/{key}</code></h3><p class="sum">Publish or replace a spatial.disco.Announce <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch. The body&#x27;s key must equal the URL&#x27;s. <code>If-Match</code> is honoured when present.</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">412</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span> <span class="s s5">503</span></p></section><section id="withdrawAnnounce"><h3><span class="v delete">DELETE</span> <code>/{scene}/{stream}/announce/v1/{key}</code></h3><p class="sum">Withdraw (dispose) a spatial.disco.Announce <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). The dispose streams on the collection&#x27;s WebSocket with its <code>reason</code>.</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listContentAnnounce"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/content_announce/v1</code></h3><p class="sum">Every spatial.disco.ContentAnnounce instance in the latched set <span class="auth">open</span></p><p>**The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="commandContentAnnounce"><h3><span class="v post">POST</span> <code>/{scene}/{stream}/content_announce/v1</code></h3><p class="sum">An N.6 command addressed through the content_announce lane <span class="auth">token</span></p><p>Accepted only on a lane whose topic advertises <code>accepts_commands</code>; otherwise <code>405</code>. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch.</p><p class="codes"><span class="s s2">202</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">409</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="getContentAnnounce"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/content_announce/v1/{key}</code></h3><p class="sum">One spatial.disco.ContentAnnounce instance <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putContentAnnounce"><h3><span class="v put">PUT</span> <code>/{scene}/{stream}/content_announce/v1/{key}</code></h3><p class="sum">Publish or replace a spatial.disco.ContentAnnounce <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch. The body&#x27;s key must equal the URL&#x27;s. <code>If-Match</code> is honoured when present.</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">412</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span> <span class="s s5">503</span></p></section><section id="withdrawContentAnnounce"><h3><span class="v delete">DELETE</span> <code>/{scene}/{stream}/content_announce/v1/{key}</code></h3><p class="sum">Withdraw (dispose) a spatial.disco.ContentAnnounce <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). The dispose streams on the collection&#x27;s WebSocket with its <code>reason</code>.</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listEntity"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/entity/v1</code></h3><p class="sum">Every spatial.owm.Entity instance in the latched set <span class="auth">token</span></p><p>**The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="commandEntity"><h3><span class="v post">POST</span> <code>/{scene}/{stream}/entity/v1</code></h3><p class="sum">An N.6 command addressed through the entity lane <span class="auth">token</span></p><p>Accepted only on a lane whose topic advertises <code>accepts_commands</code>; otherwise <code>405</code>. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch.</p><p class="codes"><span class="s s2">202</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">409</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="getEntity"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/entity/v1/{key}</code></h3><p class="sum">One spatial.owm.Entity instance <span class="auth">token</span></p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putEntity"><h3><span class="v put">PUT</span> <code>/{scene}/{stream}/entity/v1/{key}</code></h3><p class="sum">Publish or replace a spatial.owm.Entity <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch. The body&#x27;s key must equal the URL&#x27;s. <code>If-Match</code> is honoured when present. **Basis gates liveliness.** A DECLARED or AUTHORED body is a declaration and holds until withdrawn; it takes no <code>refresh</code>. An DERIVED or OBSERVED body must declare <code>refresh</code>, and be PUT again within it; when it lapses the gateway withdraws the instance itself, a terminal sample (RETIRED) then the dispose, both with the reason <code>refresh lapsed</code>. On a gateway with a DDS domain the instance is written there too, from the gateway&#x27;s own participant.</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">412</span> <span class="s s5">503</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="withdrawEntity"><h3><span class="v delete">DELETE</span> <code>/{scene}/{stream}/entity/v1/{key}</code></h3><p class="sum">Withdraw (dispose) a spatial.owm.Entity <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). A terminal sample (RETIRED, with the reason) streams first, then the dispose with its <code>reason</code>, on the collection&#x27;s WebSocket and, on a gateway with a DDS domain, on the bus.</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listGeoAnchor"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/geo_anchor/v1</code></h3><p class="sum">Every spatial.core.GeoAnchor instance in the latched set <span class="auth">open</span></p><p>**The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="commandGeoAnchor"><h3><span class="v post">POST</span> <code>/{scene}/{stream}/geo_anchor/v1</code></h3><p class="sum">An N.6 command addressed through the geo_anchor lane <span class="auth">token</span></p><p>Accepted only on a lane whose topic advertises <code>accepts_commands</code>; otherwise <code>405</code>. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch.</p><p class="codes"><span class="s s2">202</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">409</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="getGeoAnchor"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/geo_anchor/v1/{key}</code></h3><p class="sum">One spatial.core.GeoAnchor instance <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putGeoAnchor"><h3><span class="v put">PUT</span> <code>/{scene}/{stream}/geo_anchor/v1/{key}</code></h3><p class="sum">Publish or replace a spatial.core.GeoAnchor <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch. The body&#x27;s key must equal the URL&#x27;s. <code>If-Match</code> is honoured when present.</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">412</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span> <span class="s s5">503</span></p></section><section id="withdrawGeoAnchor"><h3><span class="v delete">DELETE</span> <code>/{scene}/{stream}/geo_anchor/v1/{key}</code></h3><p class="sum">Withdraw (dispose) a spatial.core.GeoAnchor <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). The dispose streams on the collection&#x27;s WebSocket with its <code>reason</code>.</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listInstances"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/{type}/{version}</code></h3><p class="sum">Any other served topic, read-only <span class="auth">token for operator-scoped</span></p><p>Latched types answer with the latched set; streamed types with the replayed window. Public or operator-scoped per the type: <code>spatial.core.FrameTransform</code> latched public, <code>spatial.events.CrossingLine</code> latched public, <code>spatial.events.SpatialEvent</code> stream bearer, <code>spatial.events.SpatialZone</code> latched public, <code>spatial.owm.ModelPose</code> stream bearer, <code>spatial.semantics.Detection2DSet</code> stream bearer, <code>spatial.semantics.Detection3DSet</code> stream bearer, <code>spatial.semantics.FusedTrackSet</code> stream bearer. **The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">401</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listWellKnownInstances"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/{stream}/{type}/{version}</code></h3><p class="sum">A four-segment topic&#x27;s collection <span class="auth">token for operator-scoped</span></p><p>A four-segment topic (<code>spatialdds/&lt;stream&gt;/&lt;type&gt;/&lt;version&gt;</code>, no scene), such as the discovery announce lane, served exactly as a scene-scoped collection is: latched set, <code>ETag</code>, <code>304</code>, the same access classes, and the N.5 stream by upgrading the same path. Read-only: a topic with no scene is in no lane. **The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">401</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="getWellKnownInstance"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/{stream}/{type}/{version}/{key}</code></h3><p class="sum">One instance of a four-segment topic <span class="auth">token for operator-scoped</span></p><p>A four-segment topic (<code>spatialdds/&lt;stream&gt;/&lt;type&gt;/&lt;version&gt;</code>, no scene), such as the discovery announce lane, served exactly as a scene-scoped collection is: latched set, <code>ETag</code>, <code>304</code>, the same access classes, and the N.5 stream by upgrading the same path. Read-only: a topic with no scene is in no lane. </p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">401</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><h2>Schemas</h2><p class=mut>115 components, from the published JSON Schemas at <code>/.well-known/spatialdds/schemas</code>.</p></main></body></html>
17
+ .s{margin-right:.5rem}.s4,.s5{color:#b5482c}a{color:var(--acc)}</style></head><body><main><h1>SpatialDDS web binding (spatialdds-py)</h1><p class=mut>Version 0.8.3 · OpenAPI 3.1.0 · generated, never hand-written · <a href='openapi'>openapi.yaml</a></p><p>Generated by <code>tools/gen_openapi.py</code> from the gateway&#x27;s own tables and the published JSON Schemas; never edited by hand. Reads are open; writes take a bearer token scoped to scenes. The WebSocket streams (N.5) are described on each collection path, not modelled. A deployment&#x27;s own keyed types (<code>serve --extension</code>) are publishable on their own collection paths, under the same rules as the entity lane when they carry <code>basis</code>, <code>state</code>, <code>state_reason</code> and <code>stamp</code>; a type that names an <code>asserter</code> has it stamped or verified as the bearer token&#x27;s principal (<code>asserter-not-principal</code>, <code>asserter-ungrounded</code>). Their paths are not listed here, because the deployment chooses them.</p><h2>Operations</h2><section id="search"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/search</code></h3><p class="sum">Services near a geohash (§8.2.3 manifests) <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span></p></section><section id="searchPost"><h3><span class="v post">POST</span> <code>/.well-known/spatialdds/search</code></h3><p class="sum">The same search, as a POST body <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="listSchemas"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/schemas</code></h3><p class="sum">The published JSON Schemas, by type <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span></p></section><section id="getSchema"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/schemas/{type}</code></h3><p class="sum"> <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="bootstrap"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/bootstrap</code></h3><p class="sum">DDS-join material (N.2); optional <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="resolverMetadata"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/resolver</code></h3><p class="sum">§7.5.2 resolver metadata <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="openapi"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/openapi</code></h3><p class="sum">This document <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span></p></section><section id="openapiPage"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/openapi.html</code></h3><p class="sum">This document as a static reference page <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">429</span></p></section><section id="resolve"><h3><span class="v get">GET</span> <code>/spatialdds/resolve</code></h3><p class="sum">Resolve a URI to its manifest (§7.5.2) <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">400</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putManifest"><h3><span class="v put">PUT</span> <code>/spatialdds/resolve</code></h3><p class="sum">Publish a manifest, keyed by its own <code>id</code> <span class="auth">token</span></p><p>The <code>uri</code> parameter is not used; the body&#x27;s <code>id</code> is the key. Its scene segment is the lane. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>).</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span> <span class="s s5">503</span></p></section><section id="withdrawManifest"><h3><span class="v delete">DELETE</span> <code>/spatialdds/resolve</code></h3><p class="sum">Withdraw a manifest; <code>resolve</code> then answers 404 <span class="auth">token</span></p><p>Owner-checked by the URI&#x27;s scene segment. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>).</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="getAsset"><h3><span class="v get">GET</span> <code>/assets/{name}</code></h3><p class="sum">Asset bytes this endpoint hosts (implementation-defined) <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="listAnnounce"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/announce/v1</code></h3><p class="sum">Every spatial.disco.Announce instance in the latched set <span class="auth">open</span></p><p>**The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="commandAnnounce"><h3><span class="v post">POST</span> <code>/{scene}/{stream}/announce/v1</code></h3><p class="sum">An N.6 command addressed through the announce lane <span class="auth">token</span></p><p>Accepted only on a lane whose topic advertises <code>accepts_commands</code>; otherwise <code>405</code>. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch.</p><p class="codes"><span class="s s2">202</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">409</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="getAnnounce"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/announce/v1/{key}</code></h3><p class="sum">One spatial.disco.Announce instance <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putAnnounce"><h3><span class="v put">PUT</span> <code>/{scene}/{stream}/announce/v1/{key}</code></h3><p class="sum">Publish or replace a spatial.disco.Announce <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch. The body&#x27;s key must equal the URL&#x27;s. <code>If-Match</code> is honoured when present.</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">412</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span> <span class="s s5">503</span></p></section><section id="withdrawAnnounce"><h3><span class="v delete">DELETE</span> <code>/{scene}/{stream}/announce/v1/{key}</code></h3><p class="sum">Withdraw (dispose) a spatial.disco.Announce <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). The dispose streams on the collection&#x27;s WebSocket with its <code>reason</code>.</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listContentAnnounce"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/content_announce/v1</code></h3><p class="sum">Every spatial.disco.ContentAnnounce instance in the latched set <span class="auth">open</span></p><p>**The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="commandContentAnnounce"><h3><span class="v post">POST</span> <code>/{scene}/{stream}/content_announce/v1</code></h3><p class="sum">An N.6 command addressed through the content_announce lane <span class="auth">token</span></p><p>Accepted only on a lane whose topic advertises <code>accepts_commands</code>; otherwise <code>405</code>. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch.</p><p class="codes"><span class="s s2">202</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">409</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="getContentAnnounce"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/content_announce/v1/{key}</code></h3><p class="sum">One spatial.disco.ContentAnnounce instance <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putContentAnnounce"><h3><span class="v put">PUT</span> <code>/{scene}/{stream}/content_announce/v1/{key}</code></h3><p class="sum">Publish or replace a spatial.disco.ContentAnnounce <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch. The body&#x27;s key must equal the URL&#x27;s. <code>If-Match</code> is honoured when present.</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">412</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span> <span class="s s5">503</span></p></section><section id="withdrawContentAnnounce"><h3><span class="v delete">DELETE</span> <code>/{scene}/{stream}/content_announce/v1/{key}</code></h3><p class="sum">Withdraw (dispose) a spatial.disco.ContentAnnounce <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). The dispose streams on the collection&#x27;s WebSocket with its <code>reason</code>.</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listEntity"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/entity/v1</code></h3><p class="sum">Every spatial.owm.Entity instance in the latched set <span class="auth">token</span></p><p>**The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="commandEntity"><h3><span class="v post">POST</span> <code>/{scene}/{stream}/entity/v1</code></h3><p class="sum">An N.6 command addressed through the entity lane <span class="auth">token</span></p><p>Accepted only on a lane whose topic advertises <code>accepts_commands</code>; otherwise <code>405</code>. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch.</p><p class="codes"><span class="s s2">202</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">409</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="getEntity"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/entity/v1/{key}</code></h3><p class="sum">One spatial.owm.Entity instance <span class="auth">token</span></p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putEntity"><h3><span class="v put">PUT</span> <code>/{scene}/{stream}/entity/v1/{key}</code></h3><p class="sum">Publish or replace a spatial.owm.Entity <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch. The body&#x27;s key must equal the URL&#x27;s. <code>If-Match</code> is honoured when present. **Basis gates liveliness.** A DECLARED or AUTHORED body is a declaration and holds until withdrawn; it takes no <code>refresh</code>. An DERIVED or OBSERVED body must declare <code>refresh</code>, and be PUT again within it; when it lapses the gateway withdraws the instance itself, a terminal sample (RETIRED) then the dispose, both with the reason <code>refresh lapsed</code>. On a gateway with a DDS domain the instance is written there too, from the gateway&#x27;s own participant.</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">412</span> <span class="s s5">503</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="withdrawEntity"><h3><span class="v delete">DELETE</span> <code>/{scene}/{stream}/entity/v1/{key}</code></h3><p class="sum">Withdraw (dispose) a spatial.owm.Entity <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). A terminal sample (RETIRED, with the reason) streams first, then the dispose with its <code>reason</code>, on the collection&#x27;s WebSocket and, on a gateway with a DDS domain, on the bus.</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listGeoAnchor"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/geo_anchor/v1</code></h3><p class="sum">Every spatial.core.GeoAnchor instance in the latched set <span class="auth">open</span></p><p>**The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="commandGeoAnchor"><h3><span class="v post">POST</span> <code>/{scene}/{stream}/geo_anchor/v1</code></h3><p class="sum">An N.6 command addressed through the geo_anchor lane <span class="auth">token</span></p><p>Accepted only on a lane whose topic advertises <code>accepts_commands</code>; otherwise <code>405</code>. **Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch.</p><p class="codes"><span class="s s2">202</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">409</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span></p></section><section id="getGeoAnchor"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/geo_anchor/v1/{key}</code></h3><p class="sum">One spatial.core.GeoAnchor instance <span class="auth">open</span></p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">404</span> <span class="s s4">429</span></p></section><section id="putGeoAnchor"><h3><span class="v put">PUT</span> <code>/{scene}/{stream}/geo_anchor/v1/{key}</code></h3><p class="sum">Publish or replace a spatial.core.GeoAnchor <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). Every member of the record is required: a body is a whole record, never a patch. The body&#x27;s key must equal the URL&#x27;s. <code>If-Match</code> is honoured when present.</p><p class="codes"><span class="s s2">200</span> <span class="s s2">201</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">405</span> <span class="s s4">412</span> <span class="s s4">429</span> <span class="s s4">413</span> <span class="s s4">415</span> <span class="s s5">503</span></p></section><section id="withdrawGeoAnchor"><h3><span class="v delete">DELETE</span> <code>/{scene}/{stream}/geo_anchor/v1/{key}</code></h3><p class="sum">Withdraw (dispose) a spatial.core.GeoAnchor <span class="auth">token</span></p><p>**Writes are per lane.** The path&#x27;s first segment is the scene; the bearer token must be granted it (<code>tenancy.WRITE_REFUSALS</code>). The dispose streams on the collection&#x27;s WebSocket with its <code>reason</code>.</p><p class="codes"><span class="s s2">204</span> <span class="s s4">400</span> <span class="s s4">401</span> <span class="s s4">403</span> <span class="s s4">404</span> <span class="s s4">405</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listInstances"><h3><span class="v get">GET</span> <code>/{scene}/{stream}/{type}/{version}</code></h3><p class="sum">Any other served topic, read-only <span class="auth">token for operator-scoped</span></p><p>Latched types answer with the latched set; streamed types with the replayed window. Public or operator-scoped per the type: <code>spatial.core.FrameTransform</code> latched public, <code>spatial.events.CrossingLine</code> latched public, <code>spatial.events.SpatialEvent</code> stream bearer, <code>spatial.events.SpatialZone</code> latched public, <code>spatial.owm.ModelPose</code> stream bearer, <code>spatial.semantics.Detection2DSet</code> stream bearer, <code>spatial.semantics.Detection3DSet</code> stream bearer, <code>spatial.semantics.FusedTrackSet</code> stream bearer. **The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">401</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="listWellKnownInstances"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/{stream}/{type}/{version}</code></h3><p class="sum">A four-segment topic&#x27;s collection <span class="auth">token for operator-scoped</span></p><p>A four-segment topic (<code>spatialdds/&lt;stream&gt;/&lt;type&gt;/&lt;version&gt;</code>, no scene), such as the discovery announce lane, served exactly as a scene-scoped collection is: latched set, <code>ETag</code>, <code>304</code>, the same access classes, and the N.5 stream by upgrading the same path. Read-only: a topic with no scene is in no lane. **The same URL is also a WebSocket** (N.5): connect with <code>wss://</code> and receive the latched set as <code>sample</code> events, exactly one <code>end_of_replay</code> carrying the replay count, then live <code>sample</code> and <code>dispose</code> events (<code>{event, topic, key, data | reason}</code>). Not modelled here; see [Appendix N.5](https://spatialdds.org/v1.8/appendix-n/).</p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">401</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><section id="getWellKnownInstance"><h3><span class="v get">GET</span> <code>/.well-known/spatialdds/{stream}/{type}/{version}/{key}</code></h3><p class="sum">One instance of a four-segment topic <span class="auth">token for operator-scoped</span></p><p>A four-segment topic (<code>spatialdds/&lt;stream&gt;/&lt;type&gt;/&lt;version&gt;</code>, no scene), such as the discovery announce lane, served exactly as a scene-scoped collection is: latched set, <code>ETag</code>, <code>304</code>, the same access classes, and the N.5 stream by upgrading the same path. Read-only: a topic with no scene is in no lane. </p><p class="codes"><span class="s s2">200</span> <span class="s s3">304</span> <span class="s s4">401</span> <span class="s s4">404</span> <span class="s s4">429</span> <span class="s s5">503</span></p></section><h2>Schemas</h2><p class=mut>115 components, from the published JSON Schemas at <code>/.well-known/spatialdds/schemas</code>.</p></main></body></html>
@@ -2,7 +2,7 @@
2
2
  openapi: 3.1.0
3
3
  info:
4
4
  title: SpatialDDS web binding (spatialdds-py)
5
- version: 0.8.2
5
+ version: 0.8.3
6
6
  summary: SpatialDDS 1.8 Appendix N over HTTPS, plus this endpoint's write surface.
7
7
  description: Generated by `tools/gen_openapi.py` from the gateway's own tables and the published JSON Schemas; never edited by hand. Reads are open; writes take a bearer token scoped to scenes. The WebSocket streams (N.5) are described on each collection path, not modelled. A deployment's own keyed types (`serve --extension`) are publishable on their own collection paths, under the same rules as the entity lane when they carry `basis`, `state`, `state_reason` and `stamp`; a type that names an `asserter` has it stamped or verified as the bearer token's principal (`asserter-not-principal`, `asserter-ungrounded`). Their paths are not listed here, because the deployment chooses them.
8
8
  servers:
@@ -11,6 +11,7 @@ import hashlib
11
11
  import json
12
12
  import math
13
13
  import re
14
+ import warnings
14
15
  from dataclasses import dataclass
15
16
  from typing import Any, Dict, Iterator, List, Optional
16
17
 
@@ -19,7 +20,7 @@ import requests
19
20
  from . import canonical, records
20
21
  from .errors import (IntegrityError, Problem, SpatialDDSError, Unreachable,
21
22
  UnverifiableIntegrity, problem_for)
22
- from .errors import SurfaceOff, Unauthorized
23
+ from .errors import AdvertisedOriginWarning, SurfaceOff, Unauthorized
23
24
 
24
25
  #: What the package's facade resolves through this module. The two exception
25
26
  #: subclasses are here because this is the module `spatialdds.Unauthorized`
@@ -29,11 +30,15 @@ __all__ = [
29
30
  "connect", "Endpoint", "Hit", "Topic", "Resolved", "Written",
30
31
  "Sample", "Dispose", "EndOfReplay", "Liveliness",
31
32
  "SpatialDDSError", "Unreachable", "Problem", "Unauthorized", "SurfaceOff",
32
- "IntegrityError", "UnverifiableIntegrity", "lift",
33
+ "IntegrityError", "UnverifiableIntegrity", "AdvertisedOriginWarning",
34
+ "lift",
33
35
  ]
34
36
 
35
37
 
36
38
  WELL_KNOWN = "/.well-known/spatialdds"
39
+ #: How many search pages `Endpoint.search` follows before it gives up
40
+ #: rather than answer with part of the list.
41
+ SEARCH_PAGES = 100
37
42
  _SHA256 = re.compile(r"^(?:sha256:)?([0-9a-f]{64})$", re.I)
38
43
  lift = canonical.lift
39
44
 
@@ -112,6 +117,7 @@ class Endpoint:
112
117
  self._cache: Dict[str, Any] = {}
113
118
  self._authority: Optional[str] = authority
114
119
  self._s = requests.Session()
120
+ self._origin_warned = False
115
121
 
116
122
  # -- plumbing --
117
123
  def _url(self, path_or_url: str) -> str:
@@ -124,6 +130,28 @@ class Endpoint:
124
130
  scheme, _, rest = url.partition("://")
125
131
  return f"{scheme.lower()}://{rest.split('/', 1)[0].lower()}"
126
132
 
133
+ def _advertised(self, url: str, where: str) -> None:
134
+ """Warn, once for this endpoint, if `url` is on another origin.
135
+
136
+ The URLs a service manifest or the resolver metadata advertise are
137
+ this endpoint's own, so their origin should be the one they were
138
+ read from. The library reads through `base` regardless (`_path_of`),
139
+ which is why a misadvertised endpoint read fine through it and
140
+ nowhere else (finding 83); this says so instead of hiding it.
141
+ """
142
+ if self._origin_warned or not url.startswith(("http://", "https://")):
143
+ return
144
+ here, there = _norm_origin(self.base), _norm_origin(url)
145
+ if here == there:
146
+ return
147
+ self._origin_warned = True
148
+ warnings.warn(AdvertisedOriginWarning(
149
+ f"{here} advertises {there} in its {where}, not the origin it "
150
+ f"was read from. This client reads through {here}; a client "
151
+ f"that follows the advertised URL reaches {there}. The endpoint "
152
+ f"is likely missing --public-url (or --authority)."),
153
+ stacklevel=3)
154
+
127
155
  def _headers(self, extra: Optional[Dict[str, str]] = None,
128
156
  url: Optional[str] = None) -> Dict[str, str]:
129
157
  """Request headers; the token only for this endpoint's own origin.
@@ -245,16 +273,39 @@ class Endpoint:
245
273
 
246
274
  # -- discovery --
247
275
  def search(self, geohash: Optional[str] = None,
248
- kinds: Optional[List[str]] = None) -> List["Hit"]:
249
- """§3.3.0 search. One cell in, service manifests out."""
276
+ kinds: Optional[List[str]] = None,
277
+ max_pages: int = SEARCH_PAGES) -> List["Hit"]:
278
+ """§3.3.0 search. One cell in, service manifests out.
279
+
280
+ Every page: a response carrying a `next_page_token` is followed with
281
+ `page_token`, the same query each time, until one carries none. 0.8.2
282
+ and earlier read only the first page, so a paging endpoint's later
283
+ services were silently missing (finding 82). An endpoint still paging
284
+ after `max_pages` pages raises rather than answering with part of
285
+ the list.
286
+ """
250
287
  params: Dict[str, Any] = {}
251
288
  if geohash:
252
289
  params["geohash"] = geohash
253
290
  if kinds:
254
291
  params["kind"] = ",".join(kinds)
255
- doc = self.get_json(f"{WELL_KNOWN}/search", params=params or None,
256
- revalidate=False)
257
- return [Hit(self, m) for m in doc.get("results", [])]
292
+ hits: List["Hit"] = []
293
+ token = ""
294
+ for _ in range(max_pages):
295
+ page = dict(params, page_token=token) if token else params
296
+ doc = self.get_json(f"{WELL_KNOWN}/search", params=page or None,
297
+ revalidate=False)
298
+ for m in doc.get("results", []):
299
+ for t in (m.get("service") or {}).get("topics") or []:
300
+ if isinstance(t, dict) and t.get("url"):
301
+ self._advertised(str(t["url"]), "search results")
302
+ hits.append(Hit(self, m))
303
+ token = doc.get("next_page_token") or ""
304
+ if not token:
305
+ return hits
306
+ raise SpatialDDSError(
307
+ f"{self.base}: search was still paging after {max_pages} pages "
308
+ f"({len(hits)} services read); pass max_pages= to read further")
258
309
 
259
310
  def near(self, lat: float, lon: float, radius_m: float) -> List["Hit"]:
260
311
  """Services whose declared coverage reaches a point.
@@ -287,6 +338,7 @@ class Endpoint:
287
338
  raise SpatialDDSError(
288
339
  f"{self.base} publishes no resolver metadata, so it resolves "
289
340
  f"nothing. It is not an authority for any spatialdds:// URI.")
341
+ self._advertised(str(hb), "resolver metadata")
290
342
  path = hb if hb.startswith(self.base) else self.base + _path_of(hb)
291
343
  r = self._request("GET", path, params={"uri": uri}, raw=True)
292
344
  return Resolved(self, uri, r.json(), r.headers.get("ETag", ""))
@@ -735,6 +787,16 @@ def _elements(cov: Any) -> List[Dict[str, Any]]:
735
787
 
736
788
  # ---- small helpers --------------------------------------------------------
737
789
 
790
+ def _norm_origin(url: str) -> str:
791
+ """scheme://host[:port], lowercased, with a scheme's default port dropped."""
792
+ scheme, _, rest = url.partition("://")
793
+ scheme, host = scheme.lower(), rest.split("/", 1)[0].lower()
794
+ default = {"http": ":80", "https": ":443"}.get(scheme)
795
+ if default and host.endswith(default):
796
+ host = host[:-len(default)]
797
+ return f"{scheme}://{host}"
798
+
799
+
738
800
  def _path_of(url: str) -> str:
739
801
  if url.startswith(("http://", "https://")):
740
802
  rest = url.split("//", 1)[1]
@@ -231,10 +231,10 @@ class Writer:
231
231
  """One lane's writer. Created by `Participant.writer`, never directly."""
232
232
 
233
233
  def __init__(self, p: "Participant", lane: _Lane, on_exit: Optional[str],
234
- floor_hz: Optional[float], validate: bool,
234
+ floor_hz: Optional[float],
235
235
  lifespan_sec: Optional[float]) -> None:
236
236
  self._p, self._lane = p, lane
237
- self.on_exit, self.floor_hz, self.validate = on_exit, floor_hz, validate
237
+ self.on_exit, self.floor_hz = on_exit, floor_hz
238
238
  self._dw = DataWriter(p._dp, p._topic(lane),
239
239
  qos=writer_qos(lane.profile, lifespan_sec))
240
240
  self._last: Dict[str, Any] = {}
@@ -289,13 +289,15 @@ class Writer:
289
289
  def write(self, sample: Any) -> Any:
290
290
  """Write a typed sample, or its N.1 document. Returns the typed sample.
291
291
 
292
- Validated against the published schema first unless the writer was
293
- made with `validate=False`; an invalid sample raises `SchemaInvalid`
294
- and nothing reaches the bus.
292
+ Validated against the published schema first, always: an invalid
293
+ sample raises `SchemaInvalid` and nothing reaches the bus. There is
294
+ no unvalidated mode. 0.8.2 and earlier had one, `validate=False`,
295
+ because validation compiled the schema per sample (finding 75); it
296
+ is compiled once a type now, and an unvalidated write is a hole in
297
+ the write contract, not a fast path.
295
298
  """
296
299
  sample = self._coerce(sample)
297
- if self.validate:
298
- codec.validate(sample)
300
+ codec.validate(sample)
299
301
  key = codec.key_of(sample)
300
302
  with self._lock:
301
303
  self._dw.write(sample)
@@ -370,7 +372,7 @@ class Writer:
370
372
  f"{', '.join(unknown)}")
371
373
  terminal = self._terminal(dataclasses.replace(last, **changes),
372
374
  reason)
373
- if changes and self.validate:
375
+ if changes:
374
376
  codec.validate(terminal)
375
377
  del self._last[key]
376
378
  # Immediately: nothing of this writer's may come between the
@@ -762,7 +764,7 @@ class Participant:
762
764
  def writer(self, type_: Union[str, type], *, stream: Optional[str] = None,
763
765
  qos_profile: Union[str, Profile, None] = None,
764
766
  on_exit: Optional[str] = None,
765
- floor_hz: Optional[float] = None, validate: bool = True,
767
+ floor_hz: Optional[float] = None,
766
768
  version: str = "v1", type_name: Optional[str] = None,
767
769
  lifespan_sec: Optional[float] = None,
768
770
  topic: Optional[str] = None) -> Writer:
@@ -810,7 +812,7 @@ class Participant:
810
812
  f"a writer does not offer a contract it will not meet")
811
813
  if problems:
812
814
  raise ParticipantRefused(problems)
813
- w = Writer(self, lane, on_exit, floor_hz, validate, lifespan_sec)
815
+ w = Writer(self, lane, on_exit, floor_hz, lifespan_sec)
814
816
  self._writers.append(w)
815
817
  for sid, svc in self._services.items():
816
818
  if svc["spec"]["topics"] is None:
@@ -363,7 +363,7 @@ def validate(sample: Any) -> Dict[str, Any]:
363
363
  check stops at the second: representable in N.1.
364
364
  """
365
365
  try:
366
- import jsonschema
366
+ import jsonschema # noqa: F401
367
367
  except ImportError as e:
368
368
  from ._extra import EXTRA, MissingExtra
369
369
  raise MissingExtra(f"validation needs jsonschema: {EXTRA}") from e
@@ -371,14 +371,16 @@ def validate(sample: Any) -> Dict[str, Any]:
371
371
  fqn = fqn_of(sample)
372
372
  if fqn in _EXT_FQN:
373
373
  return doc
374
- schema = webjson.schemas().get(fqn)
375
- if schema is None:
374
+ # Compiled once a type, a process (finding 75): a validated pose write
375
+ # cost 5-7 ms when this compiled the schema, and read every vendored
376
+ # schema from disk, per sample.
377
+ v = webjson.vendored_validator(fqn)
378
+ if v is None:
376
379
  raise SchemaInvalid(f"no published schema for {fqn}, so a sample "
377
- f"cannot be validated; pass validate=False to "
378
- f"write it unchecked")
379
- try:
380
- jsonschema.validate(doc, schema)
381
- except jsonschema.ValidationError as e:
380
+ f"cannot be validated, and nothing is written "
381
+ f"unvalidated")
382
+ e = webjson.first_error(v, doc)
383
+ if e is not None:
382
384
  where = "/".join(str(p) for p in e.absolute_path) or "(root)"
383
385
  raise SchemaInvalid(f"{fqn} at {where}: {e.message}") from e
384
386
  return doc
@@ -85,3 +85,13 @@ def problem_for(status: int, doc: Dict[str, Any], url: str,
85
85
  if status == 405 and slug == "publish-disabled":
86
86
  return SurfaceOff(status, doc, url)
87
87
  return Problem(status, doc, url)
88
+
89
+
90
+ class AdvertisedOriginWarning(UserWarning):
91
+ """An endpoint advertises URLs on an origin other than the one it was
92
+ read from (finding 83).
93
+
94
+ The library reads through the origin it connected to, so it works either
95
+ way; a client that follows the advertised URL, as N.3's hypermedia
96
+ intends, reaches the other origin. Warned once per endpoint, naming both.
97
+ """