dosync 0.6.3__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. {dosync-0.6.3 → dosync-0.7.0}/PKG-INFO +57 -17
  2. {dosync-0.6.3 → dosync-0.7.0}/README.md +49 -13
  3. {dosync-0.6.3 → dosync-0.7.0}/dosync/__init__.py +2 -2
  4. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapter_drafting.py +7 -2
  5. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/__init__.py +12 -28
  6. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/homeassistant.py +306 -61
  7. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/matter.py +37 -28
  8. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/mqtt.py +1 -1
  9. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/notifications.py +9 -9
  10. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/shelly.py +35 -26
  11. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/wiz.py +58 -28
  12. dosync-0.7.0/dosync/audit.py +809 -0
  13. {dosync-0.6.3 → dosync-0.7.0}/dosync/audit_backup.py +16 -12
  14. {dosync-0.6.3 → dosync-0.7.0}/dosync/auth.py +11 -11
  15. {dosync-0.6.3 → dosync-0.7.0}/dosync/certify.py +158 -4
  16. dosync-0.7.0/dosync/composite_executor.py +288 -0
  17. {dosync-0.6.3 → dosync-0.7.0}/dosync/dashboard.html +25 -4
  18. {dosync-0.6.3 → dosync-0.7.0}/dosync/db.py +179 -66
  19. {dosync-0.6.3 → dosync-0.7.0}/dosync/declarative.py +53 -9
  20. {dosync-0.6.3 → dosync-0.7.0}/dosync/discovery.py +4 -4
  21. {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/3d-printer.yaml +2 -2
  22. {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/air-conditioner.yaml +2 -2
  23. {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/building-lighting.json +2 -1
  24. {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/industrial-conveyor.yaml +2 -2
  25. {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/light-generic.yaml +5 -4
  26. {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/television.yaml +2 -2
  27. dosync-0.7.0/dosync/execution.py +425 -0
  28. dosync-0.7.0/dosync/hub.py +999 -0
  29. {dosync-0.6.3 → dosync-0.7.0}/dosync/manage.py +5 -5
  30. {dosync-0.6.3 → dosync-0.7.0}/dosync/mcp_server.py +245 -95
  31. {dosync-0.6.3 → dosync-0.7.0}/dosync/metrics.py +10 -0
  32. {dosync-0.6.3 → dosync-0.7.0}/dosync/models.py +177 -96
  33. dosync-0.7.0/dosync/occupancy.py +105 -0
  34. dosync-0.7.0/dosync/plan_executor.py +347 -0
  35. {dosync-0.6.3 → dosync-0.7.0}/dosync/policies.py +16 -23
  36. dosync-0.7.0/dosync/registry.py +167 -0
  37. dosync-0.7.0/dosync/resolvers.py +1176 -0
  38. dosync-0.7.0/dosync/restore.py +112 -0
  39. {dosync-0.6.3 → dosync-0.7.0}/dosync/security.py +21 -21
  40. {dosync-0.6.3 → dosync-0.7.0}/dosync/server.py +343 -39
  41. dosync-0.7.0/dosync/spec/TAG-VOCABULARY.md +332 -0
  42. dosync-0.7.0/dosync/state_refresh.py +154 -0
  43. dosync-0.7.0/dosync/telemetry.py +99 -0
  44. {dosync-0.6.3 → dosync-0.7.0}/dosync/templates/adapter-draft-prompt.md +8 -5
  45. {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/PKG-INFO +57 -17
  46. {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/SOURCES.txt +50 -1
  47. {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/requires.txt +2 -2
  48. {dosync-0.6.3 → dosync-0.7.0}/pyproject.toml +17 -4
  49. {dosync-0.6.3 → dosync-0.7.0}/tests/test_adapter_drafting.py +29 -0
  50. {dosync-0.6.3 → dosync-0.7.0}/tests/test_adapters.py +8 -4
  51. dosync-0.7.0/tests/test_adapters_speak_the_protocol.py +102 -0
  52. {dosync-0.6.3 → dosync-0.7.0}/tests/test_audit_archive.py +49 -0
  53. dosync-0.7.0/tests/test_audit_chain_ordering.py +476 -0
  54. dosync-0.7.0/tests/test_audit_concurrency.py +82 -0
  55. dosync-0.7.0/tests/test_audit_endpoint_limit.py +40 -0
  56. dosync-0.7.0/tests/test_audit_hashless.py +40 -0
  57. dosync-0.7.0/tests/test_certification_guide.py +44 -0
  58. {dosync-0.6.3 → dosync-0.7.0}/tests/test_certification_honesty.py +50 -7
  59. dosync-0.7.0/tests/test_check_helpers_can_fail.py +31 -0
  60. dosync-0.7.0/tests/test_collaborators_read_the_live_hub.py +51 -0
  61. dosync-0.7.0/tests/test_composite_executor.py +31 -0
  62. {dosync-0.6.3 → dosync-0.7.0}/tests/test_composite_operations.py +6 -2
  63. {dosync-0.6.3 → dosync-0.7.0}/tests/test_composite_orchestration.py +9 -5
  64. {dosync-0.6.3 → dosync-0.7.0}/tests/test_composition_kind_db.py +6 -2
  65. {dosync-0.6.3 → dosync-0.7.0}/tests/test_composition_kind_endpoint.py +6 -2
  66. {dosync-0.6.3 → dosync-0.7.0}/tests/test_composition_routing.py +9 -5
  67. dosync-0.7.0/tests/test_dashboard_uses_live_endpoints.py +34 -0
  68. dosync-0.7.0/tests/test_db_write_serialization.py +72 -0
  69. {dosync-0.6.3 → dosync-0.7.0}/tests/test_declarative_adapters.py +16 -8
  70. dosync-0.7.0/tests/test_declarative_location.py +108 -0
  71. {dosync-0.6.3 → dosync-0.7.0}/tests/test_deployment_env_contract.py +2 -2
  72. dosync-0.7.0/tests/test_device_location.py +245 -0
  73. {dosync-0.6.3 → dosync-0.7.0}/tests/test_direct_action_governance.py +15 -6
  74. dosync-0.7.0/tests/test_direct_action_instrumented.py +86 -0
  75. {dosync-0.6.3 → dosync-0.7.0}/tests/test_explain_consistency.py +5 -1
  76. {dosync-0.6.3 → dosync-0.7.0}/tests/test_explain_resolve_parity.py +54 -24
  77. dosync-0.7.0/tests/test_extraction_preserves_behaviour.py +635 -0
  78. {dosync-0.6.3 → dosync-0.7.0}/tests/test_geo.py +6 -2
  79. dosync-0.7.0/tests/test_ha_read_sensors.py +75 -0
  80. dosync-0.7.0/tests/test_health_recorded_once.py +82 -0
  81. dosync-0.7.0/tests/test_intent_rejections_visible.py +81 -0
  82. dosync-0.7.0/tests/test_location_restricts.py +165 -0
  83. {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_channels.py +6 -2
  84. {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_return_home.py +6 -2
  85. {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_single_reader.py +6 -2
  86. {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_telemetry_closure.py +6 -2
  87. dosync-0.7.0/tests/test_mcp_dynamic_intents.py +267 -0
  88. dosync-0.7.0/tests/test_mcp_http_transport.py +60 -0
  89. dosync-0.7.0/tests/test_mcp_scenarios_are_live.py +78 -0
  90. dosync-0.7.0/tests/test_mcp_speaks_english.py +95 -0
  91. dosync-0.7.0/tests/test_mcp_status.py +27 -0
  92. dosync-0.7.0/tests/test_measurement_tools_are_honest.py +286 -0
  93. {dosync-0.6.3 → dosync-0.7.0}/tests/test_no_operator_data.py +235 -3
  94. dosync-0.7.0/tests/test_no_undefined_names.py +41 -0
  95. dosync-0.7.0/tests/test_occupancy.py +70 -0
  96. dosync-0.7.0/tests/test_openapi_contract.py +62 -0
  97. {dosync-0.6.3 → dosync-0.7.0}/tests/test_operation_guards.py +6 -2
  98. {dosync-0.6.3 → dosync-0.7.0}/tests/test_operation_supervisor.py +6 -2
  99. dosync-0.7.0/tests/test_plan_executor.py +32 -0
  100. {dosync-0.6.3 → dosync-0.7.0}/tests/test_policies.py +7 -4
  101. dosync-0.7.0/tests/test_protocol_version.py +66 -0
  102. dosync-0.7.0/tests/test_registry.py +69 -0
  103. {dosync-0.6.3 → dosync-0.7.0}/tests/test_resolver_scoring.py +145 -1
  104. {dosync-0.6.3 → dosync-0.7.0}/tests/test_route_composer.py +6 -2
  105. dosync-0.7.0/tests/test_schemas_match_the_hub.py +163 -0
  106. {dosync-0.6.3 → dosync-0.7.0}/tests/test_server.py +27 -7
  107. dosync-0.7.0/tests/test_server_enforces_auth.py +27 -0
  108. dosync-0.7.0/tests/test_state_refresh.py +50 -0
  109. dosync-0.7.0/tests/test_tag_vocabulary.py +72 -0
  110. dosync-0.7.0/tests/test_telemetry.py +47 -0
  111. dosync-0.7.0/tests/test_undefined_name_regressions.py +53 -0
  112. {dosync-0.6.3 → dosync-0.7.0}/tests/test_universal_intent_contract.py +17 -4
  113. dosync-0.7.0/tests/test_unknown_location_refused.py +57 -0
  114. {dosync-0.6.3 → dosync-0.7.0}/tests/test_wiring_audit.py +4 -42
  115. dosync-0.7.0/tests/test_wiz_closes_every_bulb.py +76 -0
  116. dosync-0.6.3/dosync/hub.py +0 -3711
  117. dosync-0.6.3/tests/test_mcp_dynamic_intents.py +0 -177
  118. {dosync-0.6.3 → dosync-0.7.0}/LICENSE +0 -0
  119. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/ble.py +0 -0
  120. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/declarative.py +0 -0
  121. {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/mavlink.py +0 -0
  122. {dosync-0.6.3 → dosync-0.7.0}/dosync/auth_fastapi.py +0 -0
  123. {dosync-0.6.3 → dosync-0.7.0}/dosync/cert_signing.py +0 -0
  124. {dosync-0.6.3 → dosync-0.7.0}/dosync/cli.py +0 -0
  125. {dosync-0.6.3 → dosync-0.7.0}/dosync/composite_operations.py +0 -0
  126. {dosync-0.6.3 → dosync-0.7.0}/dosync/config_reference.py +0 -0
  127. {dosync-0.6.3 → dosync-0.7.0}/dosync/device_arbiter.py +0 -0
  128. {dosync-0.6.3 → dosync-0.7.0}/dosync/discoverers.py +0 -0
  129. {dosync-0.6.3 → dosync-0.7.0}/dosync/discoverers_mdns.py +0 -0
  130. {dosync-0.6.3 → dosync-0.7.0}/dosync/discoverers_ssdp.py +0 -0
  131. {dosync-0.6.3 → dosync-0.7.0}/dosync/ed25519_pure.py +0 -0
  132. {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/__init__.py +0 -0
  133. {dosync-0.6.3 → dosync-0.7.0}/dosync/executor.py +0 -0
  134. {dosync-0.6.3 → dosync-0.7.0}/dosync/geo.py +0 -0
  135. {dosync-0.6.3 → dosync-0.7.0}/dosync/hub_monitor.py +0 -0
  136. {dosync-0.6.3 → dosync-0.7.0}/dosync/lightweight.py +0 -0
  137. {dosync-0.6.3 → dosync-0.7.0}/dosync/operation_guards.py +0 -0
  138. {dosync-0.6.3 → dosync-0.7.0}/dosync/operation_supervisor.py +0 -0
  139. {dosync-0.6.3 → dosync-0.7.0}/dosync/operations.py +0 -0
  140. {dosync-0.6.3 → dosync-0.7.0}/dosync/paths.py +0 -0
  141. {dosync-0.6.3 → dosync-0.7.0}/dosync/plugins.py +0 -0
  142. {dosync-0.6.3 → dosync-0.7.0}/dosync/policy_config.py +0 -0
  143. {dosync-0.6.3 → dosync-0.7.0}/dosync/py.typed +0 -0
  144. {dosync-0.6.3 → dosync-0.7.0}/dosync/reconciler.py +0 -0
  145. {dosync-0.6.3 → dosync-0.7.0}/dosync/route_composer.py +0 -0
  146. {dosync-0.6.3 → dosync-0.7.0}/dosync/spec_coverage.py +0 -0
  147. {dosync-0.6.3 → dosync-0.7.0}/dosync/validation.py +0 -0
  148. {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/dependency_links.txt +0 -0
  149. {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/entry_points.txt +0 -0
  150. {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/top_level.txt +0 -0
  151. {dosync-0.6.3 → dosync-0.7.0}/setup.cfg +0 -0
  152. {dosync-0.6.3 → dosync-0.7.0}/tests/test_audit_backup.py +0 -0
  153. {dosync-0.6.3 → dosync-0.7.0}/tests/test_audit_chain_integrity.py +0 -0
  154. {dosync-0.6.3 → dosync-0.7.0}/tests/test_audit_provenance.py +0 -0
  155. {dosync-0.6.3 → dosync-0.7.0}/tests/test_auth.py +0 -0
  156. {dosync-0.6.3 → dosync-0.7.0}/tests/test_ble_adapter.py +0 -0
  157. {dosync-0.6.3 → dosync-0.7.0}/tests/test_claim_state_machine.py +0 -0
  158. {dosync-0.6.3 → dosync-0.7.0}/tests/test_dashboard_tells_the_truth.py +0 -0
  159. {dosync-0.6.3 → dosync-0.7.0}/tests/test_db.py +0 -0
  160. {dosync-0.6.3 → dosync-0.7.0}/tests/test_declarative_quarantine.py +0 -0
  161. {dosync-0.6.3 → dosync-0.7.0}/tests/test_deployment_layout.py +0 -0
  162. {dosync-0.6.3 → dosync-0.7.0}/tests/test_device_health.py +0 -0
  163. {dosync-0.6.3 → dosync-0.7.0}/tests/test_device_heartbeat.py +0 -0
  164. {dosync-0.6.3 → dosync-0.7.0}/tests/test_device_identity.py +0 -0
  165. {dosync-0.6.3 → dosync-0.7.0}/tests/test_discovery_adoption.py +0 -0
  166. {dosync-0.6.3 → dosync-0.7.0}/tests/test_documentation_is_navigable.py +0 -0
  167. {dosync-0.6.3 → dosync-0.7.0}/tests/test_drone_policies.py +0 -0
  168. {dosync-0.6.3 → dosync-0.7.0}/tests/test_ed25519_pure.py +0 -0
  169. {dosync-0.6.3 → dosync-0.7.0}/tests/test_emergency_preemption.py +0 -0
  170. {dosync-0.6.3 → dosync-0.7.0}/tests/test_env_loading_is_explicit.py +0 -0
  171. {dosync-0.6.3 → dosync-0.7.0}/tests/test_evaluation_metrics.py +0 -0
  172. {dosync-0.6.3 → dosync-0.7.0}/tests/test_event_loop_migration.py +0 -0
  173. {dosync-0.6.3 → dosync-0.7.0}/tests/test_ha_bridge_hygiene.py +0 -0
  174. {dosync-0.6.3 → dosync-0.7.0}/tests/test_hub_monitor.py +0 -0
  175. {dosync-0.6.3 → dosync-0.7.0}/tests/test_idempotency.py +0 -0
  176. {dosync-0.6.3 → dosync-0.7.0}/tests/test_independent_observation.py +0 -0
  177. {dosync-0.6.3 → dosync-0.7.0}/tests/test_integration_suite.py +0 -0
  178. {dosync-0.6.3 → dosync-0.7.0}/tests/test_lightweight_heartbeat.py +0 -0
  179. {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_adapter.py +0 -0
  180. {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_listener.py +0 -0
  181. {dosync-0.6.3 → dosync-0.7.0}/tests/test_mcp_partial_progress.py +0 -0
  182. {dosync-0.6.3 → dosync-0.7.0}/tests/test_metrics.py +0 -0
  183. {dosync-0.6.3 → dosync-0.7.0}/tests/test_models.py +0 -0
  184. {dosync-0.6.3 → dosync-0.7.0}/tests/test_multihub_endpoints.py +0 -0
  185. {dosync-0.6.3 → dosync-0.7.0}/tests/test_notification_templates.py +0 -0
  186. {dosync-0.6.3 → dosync-0.7.0}/tests/test_operations.py +0 -0
  187. {dosync-0.6.3 → dosync-0.7.0}/tests/test_operations_endpoints.py +0 -0
  188. {dosync-0.6.3 → dosync-0.7.0}/tests/test_operations_persistence.py +0 -0
  189. {dosync-0.6.3 → dosync-0.7.0}/tests/test_operations_wiring.py +0 -0
  190. {dosync-0.6.3 → dosync-0.7.0}/tests/test_panel_polish_2026_07_21.py +0 -0
  191. {dosync-0.6.3 → dosync-0.7.0}/tests/test_policy_config.py +0 -0
  192. {dosync-0.6.3 → dosync-0.7.0}/tests/test_public_claims.py +0 -0
  193. {dosync-0.6.3 → dosync-0.7.0}/tests/test_pushed_verification.py +0 -0
  194. {dosync-0.6.3 → dosync-0.7.0}/tests/test_quickstart_is_runnable.py +0 -0
  195. {dosync-0.6.3 → dosync-0.7.0}/tests/test_reachability_cause.py +0 -0
  196. {dosync-0.6.3 → dosync-0.7.0}/tests/test_recall_benchmark_postpolicy.py +0 -0
  197. {dosync-0.6.3 → dosync-0.7.0}/tests/test_reconciler.py +0 -0
  198. {dosync-0.6.3 → dosync-0.7.0}/tests/test_resolution_wiring.py +0 -0
  199. {dosync-0.6.3 → dosync-0.7.0}/tests/test_resolver_semantics.py +0 -0
  200. {dosync-0.6.3 → dosync-0.7.0}/tests/test_sensor_kind.py +0 -0
  201. {dosync-0.6.3 → dosync-0.7.0}/tests/test_simulation_is_declared.py +0 -0
  202. {dosync-0.6.3 → dosync-0.7.0}/tests/test_telemetry_bridge.py +0 -0
  203. {dosync-0.6.3 → dosync-0.7.0}/tests/test_third_party_adapters.py +0 -0
  204. {dosync-0.6.3 → dosync-0.7.0}/tests/test_transport_discoverers.py +0 -0
  205. {dosync-0.6.3 → dosync-0.7.0}/tests/test_validation.py +0 -0
  206. {dosync-0.6.3 → dosync-0.7.0}/tests/test_validation_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dosync
3
- Version: 0.6.3
3
+ Version: 0.7.0
4
4
  Summary: The semantic layer between AI agents and physical devices
5
5
  Author-email: Rodrigo Giuliani <rgiuliani@dosync.dev>
6
6
  License-Expression: Apache-2.0
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://dosync.dev
8
8
  Project-URL: Repository, https://github.com/giulianireg-spec/dosync-protocol
9
9
  Project-URL: Specification, https://github.com/giulianireg-spec/dosync-protocol/blob/main/spec/DoSync-SPEC-v0.1.md
10
10
  Project-URL: Issues, https://github.com/giulianireg-spec/dosync-protocol/issues
11
- Keywords: iot,ai-agents,protocol,semantic,orchestration,audit,governance,mcp,home-automation,robotics
11
+ Keywords: iot,ai-agents,protocol,semantic,orchestration,audit,governance,mcp,robotics,cyber-physical
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Intended Audience :: System Administrators
@@ -17,7 +17,11 @@ Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
20
22
  Classifier: Topic :: Home Automation
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: System :: Hardware
21
25
  Classifier: Topic :: System :: Distributed Computing
22
26
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
23
27
  Requires-Python: >=3.10
@@ -45,7 +49,7 @@ Requires-Dist: twilio>=8.0.0; extra == "sms"
45
49
  Provides-Extra: mavlink
46
50
  Requires-Dist: pymavlink>=2.4.0; extra == "mavlink"
47
51
  Provides-Extra: mcp
48
- Requires-Dist: mcp<2.0,>=1.0.0; extra == "mcp"
52
+ Requires-Dist: mcp<2.0,>=1.27.0; extra == "mcp"
49
53
  Provides-Extra: all
50
54
  Requires-Dist: pywizlight>=0.5.0; extra == "all"
51
55
  Requires-Dist: paho-mqtt>=2.0.0; extra == "all"
@@ -53,7 +57,7 @@ Requires-Dist: aiohttp>=3.9.0; extra == "all"
53
57
  Requires-Dist: bleak>=0.21; extra == "all"
54
58
  Requires-Dist: twilio>=8.0.0; extra == "all"
55
59
  Requires-Dist: pymavlink>=2.4.0; extra == "all"
56
- Requires-Dist: mcp<2.0,>=1.0.0; extra == "all"
60
+ Requires-Dist: mcp<2.0,>=1.27.0; extra == "all"
57
61
  Provides-Extra: dev
58
62
  Requires-Dist: pytest>=7.4; extra == "dev"
59
63
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
@@ -65,7 +69,7 @@ Dynamic: license-file
65
69
  > Governance and accountability for AI that acts on physical devices.
66
70
 
67
71
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
68
- [![Protocol](https://img.shields.io/badge/protocol-v0.4-green.svg)](spec/DoSync-SPEC-v0.1.md)
72
+ [![Protocol](https://img.shields.io/badge/protocol-v0.5-green.svg)](spec/DoSync-SPEC-v0.1.md)
69
73
  [![PyPI](https://img.shields.io/pypi/v/dosync.svg)](https://pypi.org/project/dosync/)
70
74
  [![Python](https://img.shields.io/pypi/pyversions/dosync.svg)](https://pypi.org/project/dosync/)
71
75
  [![CI](https://github.com/giulianireg-spec/dosync-protocol/actions/workflows/ci.yml/badge.svg)](https://github.com/giulianireg-spec/dosync-protocol/actions/workflows/ci.yml)
@@ -209,15 +213,14 @@ DoSync earns its place in specific situations — and honestly gets in the way i
209
213
  ## How it works
210
214
 
211
215
  ```
212
- User / AI says: "there is an emergency at home"
216
+ User / AI says: "there is a fire in building B"
213
217
  │
214
218
  DoSync Hub
215
219
  │
216
220
  ┌───────────────┼───────────────┐
217
221
  ▼ ▼ ▼
218
- 💡 All lights 📱 SMS sent 🚨 Alarm
219
- at maximum to family activated
220
- (10 WiZ bulbs) immediately
222
+ 💡 Every light 📱 On-call staff 🚨 Alarm
223
+ at maximum notified activated
221
224
  │ │ │
222
225
  └───────────────┴───────────────┘
223
226
  │
@@ -805,18 +808,50 @@ The server is a module rather than a command, and it needs the MCP SDK, which
805
808
  the hub does not install:
806
809
 
807
810
  ```bash
808
- pip install "mcp>=1.0.0,<2.0" # or: pipx inject dosync "mcp>=1.0.0,<2.0"
811
+ pip install "mcp>=1.27.0,<2.0" # or: pipx inject dosync "mcp>=1.27.0,<2.0"
809
812
  python -m dosync.mcp_server
810
813
  ```
811
814
 
812
- The upper bound is not caution. The 2.x SDK changed how tools are registered
813
- and this server is written against 1.x; without the cap, a fresh install gets
814
- 2.x and the server exits at import. It says so plainly if that happens.
815
+ Both bounds are measured. The upper one: the 2.x SDK changed how tools are
816
+ registered and this server is written against 1.x; without the cap, a fresh
817
+ install gets 2.x and the server exits at import. It says so plainly if that
818
+ happens. The lower one: the HTTP transport needs 1.27.0, and 1.27.0 in turn needs
819
+ pydantic 2.11 and jsonschema 4.20 — so installing the MCP server raises those
820
+ two floors above the hub's own. CI runs the whole suite on exactly those minimums.
815
821
 
816
822
  It speaks over stdin/stdout, so a successful start prints nothing and waits.
817
823
  Typing into that terminal will produce JSON parse errors — that is the protocol
818
824
  rejecting your keystrokes, not a fault.
819
825
 
826
+ **When the agent is not on this machine**, stdio cannot help: it assumes the
827
+ client can start the server as a subprocess, which means they share a filesystem.
828
+ A hub in a container, on a Pi across the room, or under a supervisor is not that.
829
+ Serve it over the network instead:
830
+
831
+ ```bash
832
+ DOSYNC_MCP_TRANSPORT=http DOSYNC_TOKEN=<token> python -m dosync.mcp_server
833
+ ```
834
+
835
+ It listens on `127.0.0.1:47210` by default; `DOSYNC_MCP_HOST` and
836
+ `DOSYNC_MCP_PORT` change that. Clients connect to `/mcp`, and `/health` answers
837
+ unauthenticated so a supervisor can check the process without holding a
838
+ credential.
839
+
840
+ **It will not start over HTTP without `DOSYNC_TOKEN`, deliberately.** Over stdio
841
+ the operating system is the boundary — whoever can start the process is already
842
+ on the machine. A port has none, and on a host network it is reachable by every
843
+ device on the LAN. The tools behind it open locks and stop machines.
844
+
845
+ **The token travels in clear text unless you put TLS in front of it.** That is
846
+ true of the hub's own API too and is documented there, but it matters more here:
847
+ this port executes tools rather than answering questions. On a trusted LAN it is
848
+ a considered trade-off; across anything else, terminate TLS in front of it. Idle
849
+ sessions are dropped after 15 minutes by default (`DOSYNC_MCP_SESSION_TIMEOUT`).
850
+
851
+ The transport changes nothing else. Both serve the same tools from the same
852
+ server object, and an agent reaching a device this way goes through the hub's
853
+ REST API exactly as `curl` does — same policy engine, same audit chain.
854
+
820
855
  It reads three environment variables:
821
856
 
822
857
  | Variable | Meaning |
@@ -904,6 +939,11 @@ it said about the second group is the part worth reading:
904
939
  > Estos tres últimos están registrados pero sin tags ni acciones definidas — el
905
940
  > hub sabe que existen pero no puede actuar sobre ellos todavía.
906
941
 
942
+ <sub>Quoted verbatim, in the language the model answered in. Translating it
943
+ would misrepresent what was said, which is the point of quoting it.
944
+ ("These last three are registered but with no tags or actions defined — the hub
945
+ knows they exist but cannot act on them yet.")</sub>
946
+
907
947
  It did not invent capabilities for devices that have none. That is not the
908
948
  model being careful: the hub reports an undeclared device as undeclared, so
909
949
  there was nothing to invent from. Then it offered to describe one — the drafting
@@ -921,11 +961,11 @@ the other end.
921
961
  | Web dashboard | ✅ |
922
962
  | API key authentication + SHA-256 audit log | ✅ |
923
963
  | Capability-based resolver | ✅ |
924
- | Certification CLI — Standard 33/33 · Emergency 44/44 (signed reports) | ✅ |
964
+ | Certification CLI — Basic 10 · Standard 33 · Emergency 44 · Conformance 65, each run in CI against a live hub (signed reports) | ✅ |
925
965
  | Philips WiZ adapter (UDP local) | ✅ |
926
966
  | Home Assistant bridge (10 domains) | ✅ |
927
967
  | Native MCP server (Claude, ChatGPT, any LLM) | ✅ |
928
- | GPIO adapter — Raspberry Pi 5 (PIR + DHT22) | ✅ |
968
+ | GPIO sensors on a Raspberry Pi 5 (PIR + DHT22), via deployment code over the API | ✅ reference deployment (not shipped) |
929
969
  | SMS notifications via Twilio | ✅ code (requires an active Twilio plan) |
930
970
  | MQTT transport adapter (Mosquitto) | ✅ |
931
971
  | Shelly adapter (HTTP local, Gen1 + Gen2) | ✅ code, not hardware-tested |
@@ -974,8 +1014,8 @@ python3 certify.py --host <hub-ip> --port 47200 --tier standard
974
1014
 
975
1015
  | Language | Location | Author | Certification |
976
1016
  |---|---|---|---|
977
- | Python (reference) | `server.py` | this project | Standard 33/33 · Emergency 44/44 ✅ |
978
- | Node.js (companion) | [giulianireg-spec/dosync-node](https://github.com/giulianireg-spec/dosync-node) | this project | Standard 33/33, against the v0.3 suite — re-validation against the current 56-test suite pending |
1017
+ | Python (reference) | `server.py` | this project | Conformance 65/65 ✅ (every tier, in CI) |
1018
+ | Node.js (companion) | [giulianireg-spec/dosync-node](https://github.com/giulianireg-spec/dosync-node) | this project | Standard 33/33, against the v0.3 suite — re-validation against the current 65-check suite pending |
979
1019
 
980
1020
  The Node.js implementation is a **companion** port that validates the protocol
981
1021
  is implementable in a second language against the same certification suite —
@@ -3,7 +3,7 @@
3
3
  > Governance and accountability for AI that acts on physical devices.
4
4
 
5
5
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
6
- [![Protocol](https://img.shields.io/badge/protocol-v0.4-green.svg)](spec/DoSync-SPEC-v0.1.md)
6
+ [![Protocol](https://img.shields.io/badge/protocol-v0.5-green.svg)](spec/DoSync-SPEC-v0.1.md)
7
7
  [![PyPI](https://img.shields.io/pypi/v/dosync.svg)](https://pypi.org/project/dosync/)
8
8
  [![Python](https://img.shields.io/pypi/pyversions/dosync.svg)](https://pypi.org/project/dosync/)
9
9
  [![CI](https://github.com/giulianireg-spec/dosync-protocol/actions/workflows/ci.yml/badge.svg)](https://github.com/giulianireg-spec/dosync-protocol/actions/workflows/ci.yml)
@@ -147,15 +147,14 @@ DoSync earns its place in specific situations — and honestly gets in the way i
147
147
  ## How it works
148
148
 
149
149
  ```
150
- User / AI says: "there is an emergency at home"
150
+ User / AI says: "there is a fire in building B"
151
151
  │
152
152
  DoSync Hub
153
153
  │
154
154
  ┌───────────────┼───────────────┐
155
155
  ▼ ▼ ▼
156
- 💡 All lights 📱 SMS sent 🚨 Alarm
157
- at maximum to family activated
158
- (10 WiZ bulbs) immediately
156
+ 💡 Every light 📱 On-call staff 🚨 Alarm
157
+ at maximum notified activated
159
158
  │ │ │
160
159
  └───────────────┴───────────────┘
161
160
  │
@@ -743,18 +742,50 @@ The server is a module rather than a command, and it needs the MCP SDK, which
743
742
  the hub does not install:
744
743
 
745
744
  ```bash
746
- pip install "mcp>=1.0.0,<2.0" # or: pipx inject dosync "mcp>=1.0.0,<2.0"
745
+ pip install "mcp>=1.27.0,<2.0" # or: pipx inject dosync "mcp>=1.27.0,<2.0"
747
746
  python -m dosync.mcp_server
748
747
  ```
749
748
 
750
- The upper bound is not caution. The 2.x SDK changed how tools are registered
751
- and this server is written against 1.x; without the cap, a fresh install gets
752
- 2.x and the server exits at import. It says so plainly if that happens.
749
+ Both bounds are measured. The upper one: the 2.x SDK changed how tools are
750
+ registered and this server is written against 1.x; without the cap, a fresh
751
+ install gets 2.x and the server exits at import. It says so plainly if that
752
+ happens. The lower one: the HTTP transport needs 1.27.0, and 1.27.0 in turn needs
753
+ pydantic 2.11 and jsonschema 4.20 — so installing the MCP server raises those
754
+ two floors above the hub's own. CI runs the whole suite on exactly those minimums.
753
755
 
754
756
  It speaks over stdin/stdout, so a successful start prints nothing and waits.
755
757
  Typing into that terminal will produce JSON parse errors — that is the protocol
756
758
  rejecting your keystrokes, not a fault.
757
759
 
760
+ **When the agent is not on this machine**, stdio cannot help: it assumes the
761
+ client can start the server as a subprocess, which means they share a filesystem.
762
+ A hub in a container, on a Pi across the room, or under a supervisor is not that.
763
+ Serve it over the network instead:
764
+
765
+ ```bash
766
+ DOSYNC_MCP_TRANSPORT=http DOSYNC_TOKEN=<token> python -m dosync.mcp_server
767
+ ```
768
+
769
+ It listens on `127.0.0.1:47210` by default; `DOSYNC_MCP_HOST` and
770
+ `DOSYNC_MCP_PORT` change that. Clients connect to `/mcp`, and `/health` answers
771
+ unauthenticated so a supervisor can check the process without holding a
772
+ credential.
773
+
774
+ **It will not start over HTTP without `DOSYNC_TOKEN`, deliberately.** Over stdio
775
+ the operating system is the boundary — whoever can start the process is already
776
+ on the machine. A port has none, and on a host network it is reachable by every
777
+ device on the LAN. The tools behind it open locks and stop machines.
778
+
779
+ **The token travels in clear text unless you put TLS in front of it.** That is
780
+ true of the hub's own API too and is documented there, but it matters more here:
781
+ this port executes tools rather than answering questions. On a trusted LAN it is
782
+ a considered trade-off; across anything else, terminate TLS in front of it. Idle
783
+ sessions are dropped after 15 minutes by default (`DOSYNC_MCP_SESSION_TIMEOUT`).
784
+
785
+ The transport changes nothing else. Both serve the same tools from the same
786
+ server object, and an agent reaching a device this way goes through the hub's
787
+ REST API exactly as `curl` does — same policy engine, same audit chain.
788
+
758
789
  It reads three environment variables:
759
790
 
760
791
  | Variable | Meaning |
@@ -842,6 +873,11 @@ it said about the second group is the part worth reading:
842
873
  > Estos tres últimos están registrados pero sin tags ni acciones definidas — el
843
874
  > hub sabe que existen pero no puede actuar sobre ellos todavía.
844
875
 
876
+ <sub>Quoted verbatim, in the language the model answered in. Translating it
877
+ would misrepresent what was said, which is the point of quoting it.
878
+ ("These last three are registered but with no tags or actions defined — the hub
879
+ knows they exist but cannot act on them yet.")</sub>
880
+
845
881
  It did not invent capabilities for devices that have none. That is not the
846
882
  model being careful: the hub reports an undeclared device as undeclared, so
847
883
  there was nothing to invent from. Then it offered to describe one — the drafting
@@ -859,11 +895,11 @@ the other end.
859
895
  | Web dashboard | ✅ |
860
896
  | API key authentication + SHA-256 audit log | ✅ |
861
897
  | Capability-based resolver | ✅ |
862
- | Certification CLI — Standard 33/33 · Emergency 44/44 (signed reports) | ✅ |
898
+ | Certification CLI — Basic 10 · Standard 33 · Emergency 44 · Conformance 65, each run in CI against a live hub (signed reports) | ✅ |
863
899
  | Philips WiZ adapter (UDP local) | ✅ |
864
900
  | Home Assistant bridge (10 domains) | ✅ |
865
901
  | Native MCP server (Claude, ChatGPT, any LLM) | ✅ |
866
- | GPIO adapter — Raspberry Pi 5 (PIR + DHT22) | ✅ |
902
+ | GPIO sensors on a Raspberry Pi 5 (PIR + DHT22), via deployment code over the API | ✅ reference deployment (not shipped) |
867
903
  | SMS notifications via Twilio | ✅ code (requires an active Twilio plan) |
868
904
  | MQTT transport adapter (Mosquitto) | ✅ |
869
905
  | Shelly adapter (HTTP local, Gen1 + Gen2) | ✅ code, not hardware-tested |
@@ -912,8 +948,8 @@ python3 certify.py --host <hub-ip> --port 47200 --tier standard
912
948
 
913
949
  | Language | Location | Author | Certification |
914
950
  |---|---|---|---|
915
- | Python (reference) | `server.py` | this project | Standard 33/33 · Emergency 44/44 ✅ |
916
- | Node.js (companion) | [giulianireg-spec/dosync-node](https://github.com/giulianireg-spec/dosync-node) | this project | Standard 33/33, against the v0.3 suite — re-validation against the current 56-test suite pending |
951
+ | Python (reference) | `server.py` | this project | Conformance 65/65 ✅ (every tier, in CI) |
952
+ | Node.js (companion) | [giulianireg-spec/dosync-node](https://github.com/giulianireg-spec/dosync-node) | this project | Standard 33/33, against the v0.3 suite — re-validation against the current 65-check suite pending |
917
953
 
918
954
  The Node.js implementation is a **companion** port that validates the protocol
919
955
  is implementable in a second language against the same certification suite —
@@ -13,5 +13,5 @@ The two numbers move independently on purpose:
13
13
  __version__ this implementation of the hub (semver)
14
14
  __protocol_version__ the wire contract other implementations must match
15
15
  """
16
- __version__ = "0.6.3"
17
- __protocol_version__ = "0.4"
16
+ __version__ = "0.7.0"
17
+ __protocol_version__ = "0.5"
@@ -79,8 +79,13 @@ def _tag_vocabulary(repo_root: Path) -> str:
79
79
  the universal intent resolves on `lock`. A model left to guess would
80
80
  reproduce that on every device it describes.
81
81
  """
82
- for candidate in (repo_root / "spec" / "TAG-VOCABULARY.md",
83
- Path(__file__).parent.parent / "spec" / "TAG-VOCABULARY.md"):
82
+ # The package's own copy first: it is what a `pip install` has. Until 0.7.0
83
+ # the wheel carried no copy, both paths below were missing in every
84
+ # installed hub, and the prompt told the model the vocabulary was
85
+ # unavailable. tests/test_tag_vocabulary.py keeps the copy identical to
86
+ # spec/TAG-VOCABULARY.md.
87
+ for candidate in (Path(__file__).parent / "spec" / "TAG-VOCABULARY.md",
88
+ repo_root / "spec" / "TAG-VOCABULARY.md"):
84
89
  if not candidate.exists():
85
90
  continue
86
91
  text = candidate.read_text(encoding="utf-8")
@@ -52,14 +52,14 @@ class DoSyncAdapter(ABC):
52
52
  """
53
53
  Base interface for physical device adapters.
54
54
 
55
- Cada adapter traduce acciones DoSync al protocolo nativo
56
- del dispositivo (UDP, HTTP, GPIO, BLE, etc.).
55
+ Each adapter translates DoSync actions into the device's native protocol
56
+ (UDP, HTTP, BLE, etc.).
57
57
 
58
58
  To implement a new adapter:
59
59
 
60
60
  class MyBrandAdapter(DoSyncAdapter):
61
61
  async def execute(self, action, urgency):
62
- # traducir action.action + action.params al protocolo del dispositivo
62
+ # translate action.action + action.params into the device's protocol
63
63
  return ActionResult(
64
64
  device_id=action.device_id,
65
65
  action=action.action,
@@ -174,16 +174,16 @@ class DoSyncAdapter(ABC):
174
174
  ...
175
175
 
176
176
 
177
- # ── AdapterExecutor — el ejecutor central ────────────────────────────────────
177
+ # ── AdapterExecutor — the central executor ────────────────────────────────────
178
178
 
179
179
  class AdapterExecutor:
180
180
  """
181
- Ejecutor central que delega acciones al adapter correcto
181
+ Central executor that delegates each action to the right adapter
182
182
  based on the 'adapter' field of the device's CapabilityManifest.
183
183
 
184
184
  A device with no registered adapter falls back to the SimulatedExecutor.
185
185
 
186
- Uso:
186
+ Usage:
187
187
  executor = AdapterExecutor(hub)
188
188
  executor.register(WiZAdapter())
189
189
  executor.register(GPIOAdapter())
@@ -195,9 +195,9 @@ class AdapterExecutor:
195
195
  def __init__(self, hub, fallback_to_simulated: bool = True):
196
196
  """
197
197
  Args:
198
- hub: instancia de DoSyncHub
199
- fallback_to_simulated: si True, dispositivos sin adapter
200
- usan SimulatedExecutor en lugar de fallar
198
+ hub: the DoSyncHub instance
199
+ fallback_to_simulated: if True, devices with no adapter
200
+ use SimulatedExecutor instead of failing
201
201
  """
202
202
  self._hub = hub
203
203
  self._adapters: dict[str, DoSyncAdapter] = {}
@@ -220,7 +220,7 @@ class AdapterExecutor:
220
220
 
221
221
 
222
222
  def registered_adapters(self) -> list[str]:
223
- """Lista de adapters registrados."""
223
+ """Names of the registered adapters."""
224
224
  return list(self._adapters.keys())
225
225
 
226
226
  async def execute(self, action: DeviceAction, urgency: Urgency) -> ActionResult:
@@ -269,8 +269,8 @@ class AdapterExecutor:
269
269
  result = await self._adapters[adapter_name].execute(action, urgency)
270
270
  if result.success:
271
271
  self._update_resolver_state(action)
272
- # Device Health Monitor — record the outcome
273
- self._record_health(action, result)
272
+ # Device health is recorded once, by the wrapper every path
273
+ # goes through (DoSyncHub.instrumented), not here as well.
274
274
  return result
275
275
  except Exception as e:
276
276
  log.error(
@@ -283,7 +283,6 @@ class AdapterExecutor:
283
283
  success=False,
284
284
  error=f"Adapter error: {e}",
285
285
  )
286
- self._record_health(action, err_result)
287
286
  return err_result
288
287
 
289
288
  # Fallback. WARNING, not INFO, and the result says it was simulated:
@@ -307,21 +306,6 @@ class AdapterExecutor:
307
306
  error=f"No adapter registered for '{adapter_name}'",
308
307
  )
309
308
 
310
- def _record_health(self, action: DeviceAction, result) -> None:
311
- """Record the outcome in the Device Health Monitor."""
312
- try:
313
- db = getattr(self._hub, 'db', None)
314
- if db:
315
- db.record_execution(
316
- device_id=action.device_id,
317
- action=action.action,
318
- success=result.success,
319
- error=getattr(result, 'error', None),
320
- )
321
- except Exception as _e:
322
- log.warning('DeviceHealthMonitor: failed to record execution for %s: %s',
323
- action.device_id, _e)
324
-
325
309
  def _update_resolver_state(self, action: DeviceAction) -> None:
326
310
  """Tell the StateAwareResolver the new state after a successful action."""
327
311
  from ..hub import StateAwareResolver