dosync 0.4.1__tar.gz → 0.6.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 (161) hide show
  1. dosync-0.6.0/PKG-INFO +902 -0
  2. dosync-0.6.0/README.md +840 -0
  3. {dosync-0.4.1 → dosync-0.6.0}/dosync/__init__.py +1 -1
  4. dosync-0.6.0/dosync/adapter_drafting.py +385 -0
  5. {dosync-0.4.1 → dosync-0.6.0}/dosync/adapters/__init__.py +90 -20
  6. {dosync-0.4.1 → dosync-0.6.0}/dosync/adapters/ble.py +61 -0
  7. dosync-0.6.0/dosync/adapters/declarative.py +241 -0
  8. {dosync-0.4.1 → dosync-0.6.0}/dosync/adapters/homeassistant.py +27 -21
  9. {dosync-0.4.1 → dosync-0.6.0}/dosync/adapters/matter.py +10 -10
  10. dosync-0.6.0/dosync/adapters/notifications.py +241 -0
  11. {dosync-0.4.1 → dosync-0.6.0}/dosync/adapters/shelly.py +16 -11
  12. {dosync-0.4.1 → dosync-0.6.0}/dosync/adapters/wiz.py +29 -20
  13. {dosync-0.4.1 → dosync-0.6.0}/dosync/audit_backup.py +43 -0
  14. {dosync-0.4.1 → dosync-0.6.0}/dosync/auth.py +61 -22
  15. {dosync-0.4.1 → dosync-0.6.0}/dosync/certify.py +118 -6
  16. {dosync-0.4.1 → dosync-0.6.0}/dosync/cli.py +18 -0
  17. dosync-0.6.0/dosync/config_reference.py +149 -0
  18. dosync-0.6.0/dosync/dashboard.html +1434 -0
  19. {dosync-0.4.1 → dosync-0.6.0}/dosync/db.py +135 -34
  20. dosync-0.6.0/dosync/declarative.py +336 -0
  21. {dosync-0.4.1 → dosync-0.6.0}/dosync/device_arbiter.py +54 -5
  22. dosync-0.6.0/dosync/discoverers.py +111 -0
  23. dosync-0.6.0/dosync/discoverers_mdns.py +242 -0
  24. dosync-0.6.0/dosync/discoverers_ssdp.py +333 -0
  25. {dosync-0.4.1 → dosync-0.6.0}/dosync/discovery.py +44 -15
  26. dosync-0.6.0/dosync/examples/__init__.py +7 -0
  27. dosync-0.6.0/dosync/examples/declarative/3d-printer.yaml +50 -0
  28. dosync-0.6.0/dosync/examples/declarative/air-conditioner.yaml +47 -0
  29. dosync-0.6.0/dosync/examples/declarative/building-lighting.json +52 -0
  30. dosync-0.6.0/dosync/examples/declarative/industrial-conveyor.yaml +52 -0
  31. dosync-0.6.0/dosync/examples/declarative/light-generic.yaml +50 -0
  32. dosync-0.6.0/dosync/examples/declarative/television.yaml +40 -0
  33. {dosync-0.4.1 → dosync-0.6.0}/dosync/executor.py +15 -3
  34. {dosync-0.4.1 → dosync-0.6.0}/dosync/hub.py +820 -32
  35. dosync-0.6.0/dosync/lightweight.py +156 -0
  36. {dosync-0.4.1 → dosync-0.6.0}/dosync/manage.py +386 -2
  37. {dosync-0.4.1 → dosync-0.6.0}/dosync/mcp_server.py +195 -67
  38. {dosync-0.4.1 → dosync-0.6.0}/dosync/models.py +119 -6
  39. {dosync-0.4.1 → dosync-0.6.0}/dosync/operation_guards.py +1 -1
  40. dosync-0.6.0/dosync/paths.py +224 -0
  41. dosync-0.6.0/dosync/plugins.py +106 -0
  42. {dosync-0.4.1 → dosync-0.6.0}/dosync/policy_config.py +3 -1
  43. {dosync-0.4.1 → dosync-0.6.0}/dosync/security.py +33 -32
  44. {dosync-0.4.1 → dosync-0.6.0}/dosync/server.py +1103 -41
  45. dosync-0.6.0/dosync/spec_coverage.py +121 -0
  46. dosync-0.6.0/dosync/templates/adapter-draft-prompt.md +49 -0
  47. dosync-0.6.0/dosync.egg-info/PKG-INFO +902 -0
  48. {dosync-0.4.1 → dosync-0.6.0}/dosync.egg-info/SOURCES.txt +44 -0
  49. {dosync-0.4.1 → dosync-0.6.0}/dosync.egg-info/requires.txt +8 -3
  50. dosync-0.6.0/pyproject.toml +128 -0
  51. dosync-0.6.0/tests/test_adapter_drafting.py +676 -0
  52. {dosync-0.4.1 → dosync-0.6.0}/tests/test_adapters.py +2 -2
  53. dosync-0.6.0/tests/test_audit_chain_integrity.py +981 -0
  54. {dosync-0.4.1 → dosync-0.6.0}/tests/test_auth.py +154 -0
  55. dosync-0.6.0/tests/test_certification_honesty.py +80 -0
  56. dosync-0.6.0/tests/test_dashboard_tells_the_truth.py +206 -0
  57. dosync-0.6.0/tests/test_declarative_adapters.py +305 -0
  58. dosync-0.6.0/tests/test_declarative_quarantine.py +196 -0
  59. dosync-0.6.0/tests/test_deployment_env_contract.py +624 -0
  60. dosync-0.6.0/tests/test_deployment_layout.py +221 -0
  61. dosync-0.6.0/tests/test_device_identity.py +157 -0
  62. dosync-0.6.0/tests/test_direct_action_governance.py +203 -0
  63. dosync-0.6.0/tests/test_discovery_adoption.py +380 -0
  64. dosync-0.6.0/tests/test_documentation_is_navigable.py +189 -0
  65. {dosync-0.4.1 → dosync-0.6.0}/tests/test_emergency_preemption.py +123 -0
  66. dosync-0.6.0/tests/test_env_loading_is_explicit.py +68 -0
  67. dosync-0.6.0/tests/test_evaluation_metrics.py +157 -0
  68. {dosync-0.4.1 → dosync-0.6.0}/tests/test_explain_consistency.py +34 -3
  69. dosync-0.6.0/tests/test_explain_resolve_parity.py +232 -0
  70. {dosync-0.4.1 → dosync-0.6.0}/tests/test_ha_bridge_hygiene.py +1 -1
  71. dosync-0.6.0/tests/test_lightweight_heartbeat.py +238 -0
  72. dosync-0.6.0/tests/test_no_operator_data.py +345 -0
  73. dosync-0.6.0/tests/test_notification_templates.py +82 -0
  74. {dosync-0.4.1 → dosync-0.6.0}/tests/test_policy_config.py +7 -1
  75. dosync-0.6.0/tests/test_public_claims.py +96 -0
  76. dosync-0.6.0/tests/test_pushed_verification.py +182 -0
  77. dosync-0.6.0/tests/test_quickstart_is_runnable.py +142 -0
  78. {dosync-0.4.1 → dosync-0.6.0}/tests/test_recall_benchmark_postpolicy.py +6 -6
  79. {dosync-0.4.1 → dosync-0.6.0}/tests/test_resolver_scoring.py +15 -1
  80. {dosync-0.4.1 → dosync-0.6.0}/tests/test_sensor_kind.py +5 -5
  81. {dosync-0.4.1 → dosync-0.6.0}/tests/test_server.py +42 -0
  82. dosync-0.6.0/tests/test_simulation_is_declared.py +295 -0
  83. dosync-0.6.0/tests/test_third_party_adapters.py +184 -0
  84. dosync-0.6.0/tests/test_transport_discoverers.py +596 -0
  85. dosync-0.6.0/tests/test_universal_intent_contract.py +202 -0
  86. {dosync-0.4.1 → dosync-0.6.0}/tests/test_validation_integration.py +5 -1
  87. dosync-0.4.1/PKG-INFO +0 -372
  88. dosync-0.4.1/README.md +0 -315
  89. dosync-0.4.1/dosync/adapters/notifications.py +0 -153
  90. dosync-0.4.1/dosync.egg-info/PKG-INFO +0 -372
  91. dosync-0.4.1/pyproject.toml +0 -79
  92. dosync-0.4.1/tests/test_deployment_env_contract.py +0 -115
  93. {dosync-0.4.1 → dosync-0.6.0}/LICENSE +0 -0
  94. {dosync-0.4.1 → dosync-0.6.0}/dosync/adapters/mavlink.py +0 -0
  95. {dosync-0.4.1 → dosync-0.6.0}/dosync/adapters/mqtt.py +0 -0
  96. {dosync-0.4.1 → dosync-0.6.0}/dosync/auth_fastapi.py +0 -0
  97. {dosync-0.4.1 → dosync-0.6.0}/dosync/cert_signing.py +0 -0
  98. {dosync-0.4.1 → dosync-0.6.0}/dosync/composite_operations.py +0 -0
  99. {dosync-0.4.1 → dosync-0.6.0}/dosync/ed25519_pure.py +0 -0
  100. {dosync-0.4.1 → dosync-0.6.0}/dosync/geo.py +0 -0
  101. {dosync-0.4.1 → dosync-0.6.0}/dosync/hub_monitor.py +0 -0
  102. {dosync-0.4.1 → dosync-0.6.0}/dosync/metrics.py +0 -0
  103. {dosync-0.4.1 → dosync-0.6.0}/dosync/operation_supervisor.py +0 -0
  104. {dosync-0.4.1 → dosync-0.6.0}/dosync/operations.py +0 -0
  105. {dosync-0.4.1 → dosync-0.6.0}/dosync/policies.py +0 -0
  106. {dosync-0.4.1 → dosync-0.6.0}/dosync/py.typed +0 -0
  107. {dosync-0.4.1 → dosync-0.6.0}/dosync/reconciler.py +0 -0
  108. {dosync-0.4.1 → dosync-0.6.0}/dosync/route_composer.py +0 -0
  109. {dosync-0.4.1 → dosync-0.6.0}/dosync/validation.py +0 -0
  110. {dosync-0.4.1 → dosync-0.6.0}/dosync.egg-info/dependency_links.txt +0 -0
  111. {dosync-0.4.1 → dosync-0.6.0}/dosync.egg-info/entry_points.txt +0 -0
  112. {dosync-0.4.1 → dosync-0.6.0}/dosync.egg-info/top_level.txt +0 -0
  113. {dosync-0.4.1 → dosync-0.6.0}/setup.cfg +0 -0
  114. {dosync-0.4.1 → dosync-0.6.0}/tests/test_audit_archive.py +0 -0
  115. {dosync-0.4.1 → dosync-0.6.0}/tests/test_audit_backup.py +0 -0
  116. {dosync-0.4.1 → dosync-0.6.0}/tests/test_audit_provenance.py +0 -0
  117. {dosync-0.4.1 → dosync-0.6.0}/tests/test_ble_adapter.py +0 -0
  118. {dosync-0.4.1 → dosync-0.6.0}/tests/test_claim_state_machine.py +0 -0
  119. {dosync-0.4.1 → dosync-0.6.0}/tests/test_composite_operations.py +0 -0
  120. {dosync-0.4.1 → dosync-0.6.0}/tests/test_composite_orchestration.py +0 -0
  121. {dosync-0.4.1 → dosync-0.6.0}/tests/test_composition_kind_db.py +0 -0
  122. {dosync-0.4.1 → dosync-0.6.0}/tests/test_composition_kind_endpoint.py +0 -0
  123. {dosync-0.4.1 → dosync-0.6.0}/tests/test_composition_routing.py +0 -0
  124. {dosync-0.4.1 → dosync-0.6.0}/tests/test_db.py +0 -0
  125. {dosync-0.4.1 → dosync-0.6.0}/tests/test_device_health.py +0 -0
  126. {dosync-0.4.1 → dosync-0.6.0}/tests/test_device_heartbeat.py +0 -0
  127. {dosync-0.4.1 → dosync-0.6.0}/tests/test_drone_policies.py +0 -0
  128. {dosync-0.4.1 → dosync-0.6.0}/tests/test_ed25519_pure.py +0 -0
  129. {dosync-0.4.1 → dosync-0.6.0}/tests/test_event_loop_migration.py +0 -0
  130. {dosync-0.4.1 → dosync-0.6.0}/tests/test_geo.py +0 -0
  131. {dosync-0.4.1 → dosync-0.6.0}/tests/test_hub_monitor.py +0 -0
  132. {dosync-0.4.1 → dosync-0.6.0}/tests/test_idempotency.py +0 -0
  133. {dosync-0.4.1 → dosync-0.6.0}/tests/test_independent_observation.py +0 -0
  134. {dosync-0.4.1 → dosync-0.6.0}/tests/test_integration_suite.py +0 -0
  135. {dosync-0.4.1 → dosync-0.6.0}/tests/test_mavlink_adapter.py +0 -0
  136. {dosync-0.4.1 → dosync-0.6.0}/tests/test_mavlink_channels.py +0 -0
  137. {dosync-0.4.1 → dosync-0.6.0}/tests/test_mavlink_listener.py +0 -0
  138. {dosync-0.4.1 → dosync-0.6.0}/tests/test_mavlink_return_home.py +0 -0
  139. {dosync-0.4.1 → dosync-0.6.0}/tests/test_mavlink_single_reader.py +0 -0
  140. {dosync-0.4.1 → dosync-0.6.0}/tests/test_mavlink_telemetry_closure.py +0 -0
  141. {dosync-0.4.1 → dosync-0.6.0}/tests/test_mcp_dynamic_intents.py +0 -0
  142. {dosync-0.4.1 → dosync-0.6.0}/tests/test_mcp_partial_progress.py +0 -0
  143. {dosync-0.4.1 → dosync-0.6.0}/tests/test_metrics.py +0 -0
  144. {dosync-0.4.1 → dosync-0.6.0}/tests/test_models.py +0 -0
  145. {dosync-0.4.1 → dosync-0.6.0}/tests/test_multihub_endpoints.py +0 -0
  146. {dosync-0.4.1 → dosync-0.6.0}/tests/test_operation_guards.py +0 -0
  147. {dosync-0.4.1 → dosync-0.6.0}/tests/test_operation_supervisor.py +0 -0
  148. {dosync-0.4.1 → dosync-0.6.0}/tests/test_operations.py +0 -0
  149. {dosync-0.4.1 → dosync-0.6.0}/tests/test_operations_endpoints.py +0 -0
  150. {dosync-0.4.1 → dosync-0.6.0}/tests/test_operations_persistence.py +0 -0
  151. {dosync-0.4.1 → dosync-0.6.0}/tests/test_operations_wiring.py +0 -0
  152. {dosync-0.4.1 → dosync-0.6.0}/tests/test_panel_polish_2026_07_21.py +0 -0
  153. {dosync-0.4.1 → dosync-0.6.0}/tests/test_policies.py +0 -0
  154. {dosync-0.4.1 → dosync-0.6.0}/tests/test_reachability_cause.py +0 -0
  155. {dosync-0.4.1 → dosync-0.6.0}/tests/test_reconciler.py +0 -0
  156. {dosync-0.4.1 → dosync-0.6.0}/tests/test_resolution_wiring.py +0 -0
  157. {dosync-0.4.1 → dosync-0.6.0}/tests/test_resolver_semantics.py +0 -0
  158. {dosync-0.4.1 → dosync-0.6.0}/tests/test_route_composer.py +0 -0
  159. {dosync-0.4.1 → dosync-0.6.0}/tests/test_telemetry_bridge.py +0 -0
  160. {dosync-0.4.1 → dosync-0.6.0}/tests/test_validation.py +0 -0
  161. {dosync-0.4.1 → dosync-0.6.0}/tests/test_wiring_audit.py +0 -0
dosync-0.6.0/PKG-INFO ADDED
@@ -0,0 +1,902 @@
1
+ Metadata-Version: 2.4
2
+ Name: dosync
3
+ Version: 0.6.0
4
+ Summary: The semantic layer between AI agents and physical devices
5
+ Author-email: Rodrigo Giuliani <rgiuliani@dosync.dev>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://dosync.dev
8
+ Project-URL: Repository, https://github.com/giulianireg-spec/dosync-protocol
9
+ Project-URL: Specification, https://github.com/giulianireg-spec/dosync-protocol/blob/main/spec/DoSync-SPEC-v0.1.md
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
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Home Automation
21
+ Classifier: Topic :: System :: Distributed Computing
22
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: fastapi<1.0,>=0.115.0
27
+ Requires-Dist: uvicorn[standard]<1.0,>=0.23.0
28
+ Requires-Dist: pydantic<3.0,>=2.7.0
29
+ Requires-Dist: jsonschema<5.0,>=4.18
30
+ Requires-Dist: bleak>=0.21
31
+ Requires-Dist: zeroconf>=0.130
32
+ Requires-Dist: pyyaml>=6.0.1
33
+ Requires-Dist: aiohttp>=3.9.0
34
+ Requires-Dist: paho-mqtt>=2.0.0
35
+ Provides-Extra: wiz
36
+ Requires-Dist: pywizlight>=0.5.0; extra == "wiz"
37
+ Provides-Extra: mqtt
38
+ Requires-Dist: paho-mqtt>=2.0.0; extra == "mqtt"
39
+ Provides-Extra: ha
40
+ Requires-Dist: aiohttp>=3.9.0; extra == "ha"
41
+ Provides-Extra: ble
42
+ Requires-Dist: bleak>=0.21; extra == "ble"
43
+ Provides-Extra: sms
44
+ Requires-Dist: twilio>=8.0.0; extra == "sms"
45
+ Provides-Extra: mavlink
46
+ Requires-Dist: pymavlink>=2.4.0; extra == "mavlink"
47
+ Provides-Extra: mcp
48
+ Requires-Dist: mcp>=1.0.0; extra == "mcp"
49
+ Provides-Extra: all
50
+ Requires-Dist: pywizlight>=0.5.0; extra == "all"
51
+ Requires-Dist: paho-mqtt>=2.0.0; extra == "all"
52
+ Requires-Dist: aiohttp>=3.9.0; extra == "all"
53
+ Requires-Dist: bleak>=0.21; extra == "all"
54
+ Requires-Dist: twilio>=8.0.0; extra == "all"
55
+ Requires-Dist: pymavlink>=2.4.0; extra == "all"
56
+ Requires-Dist: mcp>=1.0.0; extra == "all"
57
+ Provides-Extra: dev
58
+ Requires-Dist: pytest>=7.4; extra == "dev"
59
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
60
+ Requires-Dist: httpx>=0.27; extra == "dev"
61
+ Dynamic: license-file
62
+
63
+ # DoSync Protocol
64
+
65
+ > Governance and accountability for AI that acts on physical devices.
66
+
67
+ [![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)
69
+ [![PyPI](https://img.shields.io/pypi/v/dosync.svg)](https://pypi.org/project/dosync/)
70
+ [![Python](https://img.shields.io/pypi/pyversions/dosync.svg)](https://pypi.org/project/dosync/)
71
+ [![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)
72
+ [![Certification](https://img.shields.io/badge/certification-Conformance%2052%2F52-orange.svg)](dosync/certify.py)
73
+ [![MCP](https://img.shields.io/badge/MCP-compatible-purple.svg)](dosync/mcp_server.py)
74
+
75
+ ---
76
+
77
+ ## The problem
78
+
79
+ Today's IoT protocols speak the language of commands. AI speaks the language of goals.
80
+
81
+ ```python
82
+ # Existing protocols
83
+ lock.unlock()
84
+ light.set_brightness(100)
85
+ thermostat.set_temperature(21)
86
+
87
+ # What an AI actually expresses
88
+ "there is an emergency at home"
89
+ "nobody is home — save energy"
90
+ "good morning"
91
+ ```
92
+
93
+ Someone has to translate. Today, that translation is custom code written per-device, per-platform, per-scenario. It breaks when you add a new device. It completely fails in emergencies where milliseconds matter.
94
+
95
+ **DoSync is the bridge.**
96
+
97
+ ---
98
+
99
+ ## What it does
100
+
101
+ DoSync is an open protocol (Apache 2.0) that lets AI systems interact with physical devices using **semantic intent** — expressing *what they want to achieve*, not *how to achieve it*.
102
+
103
+ When the hub receives `"ensure_safety / emergency"`, every registered device figures out its own role automatically based on its declared capabilities — no hardcoded rules, no manual configuration.
104
+
105
+ ---
106
+
107
+ ## How is this different from what already exists?
108
+
109
+ A fair question, and the honest answer is that DoSync sits **above** most of what
110
+ it gets compared to, not against it.
111
+
112
+ | | What it does | What it does not decide |
113
+ |---|---|---|
114
+ | **Matter, Zigbee, MQTT** | Move commands to devices | Which device should act, or whether it should |
115
+ | **W3C Web of Things** (Thing Description) | Describe a device's properties, actions and events, with semantic annotations | Which devices serve a goal, what an operator forbids, or what happened afterwards |
116
+ | **MCP, A2A** | Connect an agent to tools | Anything about a tool being a door lock and the action being irreversible |
117
+ | **DoSync** | Resolve a goal to a plan, constrain it, execute it, and prove what happened | Transport, device description, or agent connectivity — it uses all three |
118
+
119
+ **Specifically on W3C Web of Things**, since it is the closest and the most
120
+ established: a Thing Description tells you a lock exposes a `lock` action and
121
+ how to invoke it. That is genuinely the right way to describe a device, and
122
+ DoSync does not compete with it. What a description cannot do is decide that a
123
+ lock is one of the things that should respond to *"there is an emergency"*,
124
+ refuse to touch it because this deployment forbids it, arbitrate when two
125
+ intents want it at once, or leave evidence afterwards that survives someone with
126
+ root access. Those are the questions DoSync answers.
127
+
128
+ **On MCP**: DoSync ships an MCP server. It is a distribution channel, not a
129
+ rival. MCP is how an agent reaches DoSync; DoSync is what happens between the
130
+ agent's goal and a device moving.
131
+
132
+ ### The five things
133
+
134
+ Everything above reduces to five properties. Each is verifiable in a running
135
+ hub — the numbers below come from the reference deployment, not from a
136
+ brochure:
137
+
138
+ 1. **Explainable resolution.** `GET /v1/intents/{class}/explain` returns which
139
+ devices were evaluated, which were included, and the score breakdown behind
140
+ each. The score it reports is the same value the resolver decided with — one
141
+ computation, not a narration of one.
142
+ 2. **Policies the AI cannot route around.** A deployment declares what must not
143
+ happen; every path to a device is evaluated against it, including direct
144
+ device actions and the MCP tool. This was not true until we audited our own
145
+ claim and found the hole.
146
+ 3. **A record that resists tampering — and says where it stops.** SHA-256 chain
147
+ with policy provenance, plus sequence numbers, a head mark and signed
148
+ exportable checkpoints. What it detects and what it cannot is written down in
149
+ [the threat model](docs/AUDIT-THREAT-MODEL.md), including the rows that read
150
+ "not detected".
151
+ 4. **Formal arbitration of physical conflict.** A per-device claim state machine
152
+ with stated invariants ([consistency model §3.1](spec/CONSISTENCY-MODEL.md)), so an
153
+ emergency and a routine wanting the same device is resolved by rule rather
154
+ than by timing.
155
+ 5. **Failure semantics that do not lie.** `contradicted` (the device said yes,
156
+ the sensor disagrees) is distinct from `unverifiable` (we could not look), and
157
+ `likely_powered_off` from `indeterminate`. The system says what it does not
158
+ know.
159
+
160
+ None of these is claimed to be unbreakable. Claim 3 in particular has documented
161
+ limits, on purpose: a protocol whose value is honesty cannot make absolute
162
+ security claims and stay coherent.
163
+
164
+ ---
165
+
166
+ ## Scope and safety boundaries
167
+
168
+ DoSync coordinates **non-safety-critical systems** — lighting, access, climate, notifications, logging — and produces a tamper-evident record of every action. It is infrastructure for coordination and auditability, not a certified safety system.
169
+
170
+ DoSync is **not** certified to IEC 61508 / IEC 62304 / ISO 13849 and must not be the sole or primary mechanism for:
171
+
172
+ - Primary control of medical devices or life-support systems
173
+ - Fire suppression, gas detection, or emergency shutdown of SIL-rated machinery
174
+ - Any function where a failure could cause injury or loss of life
175
+
176
+ In regulated or industrial environments, DoSync **complements** the certified safety systems already in place — coordinating the peripherals around them and recording what happened — but never replaces them. The certified safety system remains in charge of safety.
177
+
178
+ See [Protocol Specification §12.3](spec/DoSync-SPEC-v0.1.md) for the full operational boundaries.
179
+
180
+ ---
181
+
182
+ ## Is DoSync for you?
183
+
184
+ DoSync earns its place in specific situations — and honestly gets in the way in others. A quick filter:
185
+
186
+ **It probably fits if you:**
187
+ - Are building an **AI agent that acts on physical devices** and need an auditable record of what it did, when, and why.
188
+ - Coordinate **heterogeneous devices** (different brands / transports) and want one semantic layer — express a goal, devices resolve it — with a tamper-evident audit trail.
189
+ - Work in **robotics or physical automation** and want a policy + safety layer (emergency preemption, confirmation policies) *between* the AI and the actuators.
190
+ - Keep hand-writing per-device command sequences and wish you could just say *"secure the space."*
191
+
192
+ **It's probably not for you if you:**
193
+ - Want home automation (schedules, motion → light). [Home Assistant](https://www.home-assistant.io/) and its automations — and its MCP server — already do that better; DoSync would be overhead.
194
+ - Have a single device or one brand's ecosystem — you don't need a coordination layer.
195
+ - Don't need auditability or a policy layer.
196
+
197
+ **If the first list is you:** the fastest way in is the **[20-minute device tutorial](docs/DEVICE-INTEGRATION.md)** — build a device that senses, expresses a goal, and acts, with the full audit trail. Or open an [issue](https://github.com/giulianireg-spec/dosync-protocol/issues) describing what you're trying to coordinate and we'll tell you honestly whether DoSync fits.
198
+
199
+ ---
200
+
201
+ ## Demo
202
+
203
+ [![DoSync Demo](https://img.shields.io/badge/▶_Watch_Demo-YouTube-red?style=for-the-badge)](https://youtu.be/2czAqoIrd08)
204
+
205
+ **What you'll see:** Claude AI triggers a physical emergency protocol in real time — 10 Philips WiZ bulbs at full brightness, SMS notification sent, audit log updated. No commands. No rules. No cloud.
206
+
207
+ ---
208
+
209
+ ## How it works
210
+
211
+ ```
212
+ User / AI says: "there is an emergency at home"
213
+ │
214
+ DoSync Hub
215
+ │
216
+ ┌───────────────┼───────────────┐
217
+ ▼ ▼ ▼
218
+ 💡 All lights 📱 SMS sent 🚨 Alarm
219
+ at maximum to family activated
220
+ (10 WiZ bulbs) immediately
221
+ │ │ │
222
+ └───────────────┴───────────────┘
223
+ │
224
+ Audit log updated
225
+ (SHA-256 tamper-evident)
226
+ ```
227
+
228
+ The **Capability-based Resolver** matches the intent against every device's **Capability Manifest** — what it can sense, what it can do, whether it's emergency-capable. No rules to write. Add a new device and it participates automatically.
229
+
230
+ Benchmark (Raspberry Pi 5, Python 3.11.2):
231
+
232
+ | Devices | Mean | p99 | Within 500ms limit |
233
+ |---|---|---|---|
234
+ | 38 (production) | 0.076ms | 0.097ms | ✓ |
235
+ | 1000 | 1.336ms | 5.690ms | ✓ |
236
+ | 5000 | 9.163ms | 24.541ms | ✓ (20× margin) |
237
+
238
+ ---
239
+
240
+ ## Protocol architecture
241
+
242
+ | Layer | Name | Role |
243
+ |---|---|---|
244
+ | 5 | **Intent** | AI expresses semantic goals |
245
+ | 4 | **Semantic** | Resolver maps intent → device actions |
246
+ | 3 | **Registry** | Devices self-declare capabilities on join |
247
+ | 2 | **Secure channel** | mTLS, local PKI — no internet required |
248
+ | 1 | **Transport (HAL)** | Reference: WiFi/HTTP-WS · MQTT. Via bridge: Zigbee · Z-Wave · Thread · Matter (Home Assistant). Native BLE/radio bindings: roadmap |
249
+
250
+ ---
251
+
252
+ ## Where to start
253
+
254
+ | You want to… | Go to |
255
+ |---|---|
256
+ | **run a hub and see your devices** | [Quick start](#quick-start), below — about five minutes, no terminal after the first command |
257
+ | **build a device that speaks the protocol** | [docs/DEVICE-INTEGRATION.md](docs/DEVICE-INTEGRATION.md) — needs Docker, a broker and Python |
258
+ | **implement DoSync yourself, or check conformance** | [spec/](spec/) — the wire format, the JSON schemas, and `certify.py` |
259
+ | **understand why it is built this way** | [DESIGN-PRINCIPLES.md](DESIGN-PRINCIPLES.md) |
260
+ | **know its scope, or who decides** | [docs/VISION.md](docs/VISION.md) · [GOVERNANCE.md](GOVERNANCE.md) |
261
+
262
+ Three of those four were reachable only by guessing. The one called `TUTORIAL.md`
263
+ sat in the repository root, where a reader looks first, and asked for Docker in
264
+ its first step.
265
+
266
+ ## Quick start
267
+
268
+ ### Install and run — five minutes, no hardware
269
+
270
+ ```bash
271
+ pipx install dosync # recommended
272
+ dosync-hub
273
+ ```
274
+
275
+ <details>
276
+ <summary><b>On Windows</b> — pipx is not installed with Python there</summary>
277
+
278
+ Every step below was found by running it on a clean Windows machine. None of it
279
+ is exotic; it is simply what Windows needs and what this page used to omit.
280
+
281
+ ```powershell
282
+ python -m pip install --user pipx
283
+ python -m pipx ensurepath
284
+ ```
285
+
286
+ **Close PowerShell and open it again.** `ensurepath` edits your PATH and the
287
+ session you are in cannot see the change. Then:
288
+
289
+ ```powershell
290
+ pipx install dosync
291
+ dosync-hub
292
+ ```
293
+
294
+ Two things the rest of this page assumes that PowerShell does not provide:
295
+ `export VAR=value` is `$env:VAR = "value"`, and `curl` is an alias for
296
+ `Invoke-WebRequest`, which is a different program with a different syntax — use
297
+ `curl.exe`, or the PowerShell examples further down. `setup_pki.sh` is a shell
298
+ script and needs Git Bash or WSL.
299
+
300
+ If a control library for your hardware needs installing afterwards, a plain
301
+ `pip install` will not reach it — `pipx` keeps the hub in its own isolated
302
+ environment. See **pipx inject**, below.
303
+
304
+ </details>
305
+
306
+ ### Then open the dashboard
307
+
308
+ ```
309
+ http://localhost:47200
310
+ ```
311
+
312
+ Paste the API key the hub printed on startup, press **Scan**, and the hub
313
+ searches every transport it can reach — WiFi broadcast, mDNS, SSDP, Bluetooth —
314
+ and shows what answered. Name anything you want to keep and it is adopted.
315
+
316
+ **Start here.** Discovering and adopting a device needs no terminal and no
317
+ JSON: a 3D printer, a television and a Bluetooth sensor were adopted this way
318
+ on the reference deployment without a line being typed. The API calls below are
319
+ for building against DoSync, not for setting it up — they came first on this
320
+ page for a long time, which told people who do not write code that the project
321
+ was not for them, while a button that did the same job sat one section lower.
322
+
323
+ <details>
324
+ <summary><b>"error: externally-managed-environment"?</b> — Raspberry Pi OS, Debian 12+, Ubuntu 23.04+</summary>
325
+
326
+ Those systems refuse system-wide `pip install` (PEP 668) to stop Python packages
327
+ from breaking the OS. This hits the Raspberry Pi first, which is the most likely
328
+ machine to be running a hub, so it is worth getting right rather than working
329
+ around.
330
+
331
+ **`pipx` is the correct tool here** and not a workaround: DoSync is an
332
+ application with commands you run, not a library you import into your own code.
333
+ pipx gives it a private environment and still puts `dosync-hub`,
334
+ `dosync-manage` and `dosync-certify` on your PATH.
335
+
336
+ ```bash
337
+ sudo apt install pipx # once
338
+ pipx ensurepath # once; open a new shell afterwards
339
+ pipx install dosync
340
+ ```
341
+
342
+ If you are writing Python against DoSync rather than running the hub, a virtual
343
+ environment is the right choice instead:
344
+
345
+ ```bash
346
+ python3 -m venv ~/dosync-env
347
+ ~/dosync-env/bin/pip install dosync
348
+ ~/dosync-env/bin/dosync-hub
349
+ ```
350
+
351
+ `pip install --break-system-packages dosync` also works and is the one option we
352
+ would not recommend: it installs into the system Python that your OS depends on,
353
+ which is the situation PEP 668 exists to prevent.
354
+ </details>
355
+
356
+ That is a working hub on `http://127.0.0.1:47200`. It starts with a simulated
357
+ executor, so you can drive the whole protocol — register devices, fire intents,
358
+ read the audit chain — before you own a single smart device.
359
+
360
+ ### Or drive it from the API
361
+
362
+ Everything the dashboard does is an HTTP call, and this is the part to read if
363
+ you are building against DoSync rather than setting it up.
364
+
365
+ The hub prints an API key on that first start and stores only a hash of it, so
366
+ it is shown once. Save it — every command below needs it:
367
+
368
+ ```bash
369
+ export DOSYNC_TOKEN=<the key printed on first start>
370
+ ```
371
+
372
+ ```powershell
373
+ $env:DOSYNC_TOKEN = "<the key printed on first start>"
374
+ ```
375
+
376
+ Register something and give it a goal:
377
+
378
+ ```bash
379
+ # 1. A device declares what it CAN DO (not what commands it takes)
380
+ curl -X POST http://127.0.0.1:47200/v1/devices/register \
381
+ -H "Authorization: Bearer $DOSYNC_TOKEN" \
382
+ -H 'Content-Type: application/json' -d '{
383
+ "device_id": "siren-hall", "device_name": "Hall Siren",
384
+ "manufacturer": "acme", "model": "S1", "firmware": "1.0",
385
+ "category": "actuator", "tags": ["alarm", "emergency"],
386
+ "emergency_capable": true, "cert_tier": "basic",
387
+ "sensors": [], "actuators": [{"id": "alarm", "type": "alarm",
388
+ "description": "audible alarm"}]}'
389
+
390
+ # 2. An AI expresses a GOAL — not a command, and it names no device
391
+ curl -X POST http://127.0.0.1:47200/v1/intent/async \
392
+ -H "Authorization: Bearer $DOSYNC_TOKEN" \
393
+ -H 'Content-Type: application/json' \
394
+ -d '{"intent": "ensure_safety", "urgency": "emergency", "context": {}}'
395
+
396
+ # 3. Ask WHY those devices were chosen
397
+ curl http://127.0.0.1:47200/v1/intents/ensure_safety/explain \
398
+ -H "Authorization: Bearer $DOSYNC_TOKEN"
399
+
400
+ # 4. Read the tamper-evident record of what happened
401
+ curl "http://127.0.0.1:47200/v1/audit?limit=10" \
402
+ -H "Authorization: Bearer $DOSYNC_TOKEN"
403
+ ```
404
+
405
+ <details>
406
+ <summary><b>The same calls in PowerShell</b></summary>
407
+
408
+ `curl` in PowerShell is an alias for `Invoke-WebRequest`, and quoting JSON for
409
+ `curl.exe` is impractical — a clean Windows machine following the commands above
410
+ verbatim gets `JSON decode error`, because PowerShell passes the escaped quotes
411
+ through literally. Build the body as an object instead:
412
+
413
+ ```powershell
414
+ $body = @{
415
+ device_id = "siren-hall"; device_name = "Hall Siren"
416
+ manufacturer = "acme"; model = "S1"; firmware = "1.0"
417
+ category = "actuator"; tags = @("alarm","emergency")
418
+ emergency_capable = $true; cert_tier = "basic"
419
+ capabilities = @{ sensors = @(); actuators = @(@{ id="alarm"; type="alarm"; description="sound" }) }
420
+ } | ConvertTo-Json -Depth 6
421
+
422
+ $headers = @{ Authorization = "Bearer $env:DOSYNC_TOKEN" }
423
+
424
+ Invoke-RestMethod -Method Post -Uri http://127.0.0.1:47200/v1/devices/register `
425
+ -Headers $headers -ContentType "application/json" -Body $body
426
+
427
+ Invoke-RestMethod -Method Post -Uri http://127.0.0.1:47200/v1/intent/async `
428
+ -Headers $headers -ContentType "application/json" `
429
+ -Body (@{ intent = "ensure_safety"; urgency = "emergency"; context = @{} } | ConvertTo-Json)
430
+
431
+ Invoke-RestMethod -Uri http://127.0.0.1:47200/v1/intents/ensure_safety/explain -Headers $headers
432
+ Invoke-RestMethod -Uri "http://127.0.0.1:47200/v1/audit?limit=10" -Headers $headers
433
+ ```
434
+
435
+ </details>
436
+ ```
437
+
438
+ Step 3 is the one worth pausing on: the hub tells you which devices it
439
+ evaluated, which it included, and the score breakdown behind each decision.
440
+ Step 4 is the other: every action leaves a SHA-256-chained entry, so what the
441
+ system did is provable after the fact rather than merely logged.
442
+
443
+ **Discovery works out of the box.** The library that finds Bluetooth devices
444
+ ships in the core install, and the BLE adapter registers itself when it is
445
+ available — because discovery is how you learn what you have. Requiring an extra
446
+ first would be a circle: nobody installs a Bluetooth library before knowing they
447
+ own Bluetooth devices, and nobody can find out without it. A hub with no radio
448
+ loses nothing — the scan reports the transport as unsearchable rather than
449
+ failing — and `pip uninstall bleak` or `DOSYNC_BLE_ENABLED=false` removes it.
450
+
451
+ **On the adapters that ship with DoSync.** They come in three kinds, visible at
452
+ `GET /v1/adapters`. **Ecosystem** adapters implement open standards — MQTT,
453
+ Matter, BLE, MAVLink, and the Home Assistant bridge, which is the widest door of
454
+ all: anything HA already integrates, DoSync can reach. **Reference** adapters
455
+ (WiZ, Shelly) implement one vendor's product and ship as worked examples of how
456
+ an adapter is written — not as endorsement, partnership, or a promise to track
457
+ anyone's firmware. **They register only when their vendor library is installed,
458
+ and say nothing when it is not.** A hub whose operator owns nothing from that
459
+ vendor should never be told to install anything: the protocol does not presume
460
+ your hardware, and a reference adapter that nagged for `pip install pywizlight`
461
+ on every start was presuming it. If you do register a device that names an
462
+ adapter the hub cannot load, the startup check names that device and tells you
463
+ its actions will be simulated. **Infrastructure** is notifications.
464
+
465
+ **If your device is not covered, describe it in a file.** A declarative adapter
466
+ is YAML or JSON — no code, no release of DoSync to wait for:
467
+
468
+ ```yaml
469
+ device:
470
+ id: light-hallway
471
+ name: Hallway light
472
+ tags: [light, energy] # how intents find it
473
+ emergency_capable: true # whether an emergency may use it
474
+
475
+ transport:
476
+ kind: http
477
+ base_url: http://192.168.1.40
478
+
479
+ actions:
480
+ turn_on:
481
+ type: turn_on # what it MEANS to DoSync, not just its name
482
+ request: { method: POST, path: /light/on }
483
+ ```
484
+
485
+ Drop it in `declarative/` (or set `DOSYNC_DECLARATIVE_DIR`) and restart.
486
+
487
+ **You do not have to write the first one.** Six worked examples ship with the
488
+ package — a light, an air conditioner, a 3D printer, a television, a floor's
489
+ lighting controller and an industrial conveyor over MQTT — chosen so one of them
490
+ probably resembles what you have:
491
+
492
+ ```bash
493
+ dosync-manage examples # copies them into declarative/, ready to edit
494
+ ```
495
+
496
+ They are also readable in the repository at
497
+ [`examples/declarative/`](examples/declarative/).
498
+
499
+ The `type` on each action is the part that matters. A file that only said "POST
500
+ /on turns it on" would let DoSync switch the device and leave it invisible to
501
+ everything else: no intent could select it, no policy could name it, an
502
+ emergency would pass it by.
503
+
504
+ **What a declarative adapter cannot do**, stated plainly: it speaks HTTP. It
505
+ cannot speak Zigbee, Z-Wave, BLE pairing, an OPC-UA session, or anything needing
506
+ a handshake, session state or a vendor SDK. Those need a code adapter — an
507
+ ecosystem one here, or a third-party package. This format covers most simple
508
+ devices and almost no complex ones.
509
+
510
+ **If it needs real code — pairing, a session, a vendor SDK — publish a package.**
511
+ DoSync discovers adapters advertised by anything installed alongside it:
512
+
513
+ ```toml
514
+ # in the vendor's pyproject.toml
515
+ [project.entry-points."dosync.adapters"]
516
+ daikin = "dosync_adapter_daikin:DaikinAdapter"
517
+ ```
518
+
519
+ The operator runs `pip install dosync-adapter-daikin` and the hub finds it. No
520
+ pull request here, and no promise from this project to maintain code for
521
+ hardware it has never seen — the publisher answers for their own adapter.
522
+
523
+ **If DoSync itself was installed with `pipx`, a plain `pip install` lands in
524
+ the wrong environment.** `pipx` deliberately isolates the hub in its own
525
+ virtual environment, separate from the system Python, so a vendor library
526
+ installed the ordinary way is invisible to it — the hub stays silent about
527
+ this exactly as it does about an uninstalled one, because from its side
528
+ there is no difference. Install into the hub's own environment instead:
529
+
530
+ ```bash
531
+ pipx inject dosync dosync-adapter-daikin
532
+ ```
533
+
534
+ This is not specific to any one vendor's product: it is how you add *any*
535
+ optional dependency — a control library, a third-party adapter package, a
536
+ protocol SDK — after installing DoSync with `pipx`. Whichever hardware you
537
+ own, `pipx inject dosync <package>` is the command, not `pip install`.
538
+
539
+ A third-party adapter runs inside the hub with the hub's permissions, so the hub
540
+ says so: it is logged at WARNING when loaded, recorded in the audit chain, and
541
+ reported as `kind: third_party` at `/v1/adapters` regardless of what the plugin
542
+ declares about itself. Where code came from is not the code's to assert.
543
+
544
+ DoSync does not download an adapter for you: the
545
+ protocol's whole argument is that nothing actuates hardware without a policy and
546
+ a record, and fetching executable code from the internet would put the largest
547
+ possible hole exactly there. Instead, an operator writes a declarative adapter
548
+ for HTTP/MQTT/Modbus devices, or installs a third-party package deliberately.
549
+
550
+ Install only the CONTROL adapters you need — those follow the opposite rule,
551
+ since you already know which hardware you own:
552
+
553
+ ```bash
554
+ pip install 'dosync[wiz]' # Philips WiZ bulbs
555
+ pip install 'dosync[ha]' # Home Assistant bridge
556
+ pip install 'dosync[mqtt]' # MQTT devices
557
+ pip install 'dosync[all]' # everything
558
+ ```
559
+
560
+ ### Access: a password you choose, or none at all
561
+
562
+ The hub prints a token on first start and stores only a hash of it, so it cannot
563
+ show you that one again. You are not stuck with it.
564
+
565
+ **From the dashboard** (the ⚙ button, once connected): set a password of your
566
+ choosing, or turn the token requirement off entirely. No shell, no unit file.
567
+
568
+ **From a terminal**, if you prefer:
569
+
570
+ ```bash
571
+ # Choose your own — for a person who has to type it
572
+ dosync-manage keys create --token "my-house-2026-kitchen" --label dashboard
573
+
574
+ # Let it generate one — for a program that will store it
575
+ dosync-manage keys create --label my-integration
576
+
577
+ # Start with no authentication at all
578
+ DOSYNC_AUTH=false dosync-hub
579
+ ```
580
+
581
+ **Running without a token is a legitimate choice, not a trap door.** On a home
582
+ network, behind a router, with no port forwarding, a token protects against
583
+ nobody who is not already inside your house. It is the wrong default for a
584
+ clinic and an unnecessary obstacle for a workshop, so DoSync offers it plainly
585
+ rather than assuming everyone shares one threat model. It is not suitable for
586
+ any hub reachable from outside its own network.
587
+
588
+ Two things worth knowing:
589
+
590
+ - **`DOSYNC_AUTH` in the environment wins.** If it is set in your service
591
+ configuration, the dashboard will tell you so and refuse to override it — a
592
+ click in a browser should not quietly undo what the machine was told to do.
593
+ - **Changing access is recorded.** Setting a password or turning authentication
594
+ off appends to the audit chain, so "when did this hub become open, and who did
595
+ it" has an answer. The token value itself is never written there.
596
+
597
+ A chosen password must be at least 12 characters, and a passphrase of several
598
+ words is better than a short clever string: a bearer token is checked with no
599
+ rate limiting and no lockout, so it is guessed offline at full speed.
600
+
601
+ Existing keys: `dosync-manage keys list` (previews only — they are hashed),
602
+ `keys revoke <preview>`, `keys reset`.
603
+
604
+ ### Hardware that cannot do TLS
605
+
606
+ A sensor running a year on a coin cell cannot perform a TLS handshake — it costs
607
+ more battery than a month of operation. Such a device can still report liveness,
608
+ signed rather than encrypted:
609
+
610
+ ```bash
611
+ DOSYNC_LIGHTWEIGHT_HEARTBEAT=true dosync-hub
612
+ ```
613
+
614
+ `POST /v1/heartbeat/signed` accepts a heartbeat authenticated by HMAC over the
615
+ device's provisioning token. It is **off by default**, and it is worth knowing
616
+ exactly what it trades before turning it on: the channel provides message
617
+ authenticity and replay resistance, and **no confidentiality** — the device id,
618
+ timestamp and report travel readable. Devices using it are marked
619
+ `report_channel: signed_plaintext` so they are distinguishable from ones on mTLS.
620
+
621
+ That trade is defensible for a heartbeat and would not be for an action: a
622
+ heartbeat is positive signal only, so a forged one cannot switch anything on.
623
+ The attack it invites is replay — repeating a captured message to keep a failed
624
+ device reporting healthy — and that is closed. See spec §7.10 and
625
+ [the threat model](docs/AUDIT-THREAT-MODEL.md).
626
+
627
+ ### TLS, and why your browser says "Not secure"
628
+
629
+ `bash setup_pki.sh` creates a private certificate authority in `certs/` and
630
+ issues the hub a certificate from it. Start the hub with those files and traffic
631
+ is encrypted:
632
+
633
+ ```bash
634
+ dosync-hub --host 0.0.0.0 & # or with uvicorn's --ssl-keyfile / --ssl-certfile
635
+ ```
636
+
637
+ Your browser will then show **"Not secure"** with `https` struck through. This
638
+ is expected and it does **not** mean the connection is unencrypted. It means the
639
+ browser does not recognise the authority that signed the certificate — which is
640
+ you. A public CA cannot issue a certificate for `192.168.x.x`, so a hub on a
641
+ private network is always in this position.
642
+
643
+ Two honest options:
644
+
645
+ **Accept the warning.** Click through it. The connection is encrypted; what is
646
+ missing is a third party vouching that the server is who it claims. On your own
647
+ LAN, where you set up the hub yourself, that is a much smaller gap than it looks.
648
+
649
+ **Trust your own CA, and the warning goes away** — on the machines you choose:
650
+
651
+ ```bash
652
+ # macOS
653
+ sudo security add-trusted-cert -d -r trustRoot \
654
+ -k /Library/Keychains/System.keychain certs/ca.crt
655
+
656
+ # Linux (Debian/Ubuntu)
657
+ sudo cp certs/ca.crt /usr/local/share/ca-certificates/dosync-ca.crt
658
+ sudo update-ca-certificates
659
+
660
+ # Windows (PowerShell, as Administrator)
661
+ Import-Certificate -FilePath ca.crt -CertStoreLocation Cert:\LocalMachine\Root
662
+ ```
663
+
664
+ Copy `certs/ca.crt` from the hub first — it is the only file you need, and it
665
+ contains no secret. The hub's private key (`certs/hub.key`) never leaves the
666
+ hub.
667
+
668
+ **What the warning does mean.** If you see it on a hub you did not set up, or on
669
+ a network you do not control, do not click through — that is exactly the case
670
+ the warning exists for.
671
+
672
+ ### Docker
673
+
674
+ ```bash
675
+ docker run -p 47200:47200 dosync/hub # published image
676
+ # or, from a clone:
677
+ docker compose up
678
+ ```
679
+
680
+ ### From source (development)
681
+
682
+ ```bash
683
+ git clone https://github.com/giulianireg-spec/dosync-protocol
684
+ cd dosync-protocol
685
+ python3 -m venv venv && source venv/bin/activate
686
+ pip install -e '.[dev]'
687
+ pytest # runs the full suite
688
+ dosync-hub --reload
689
+ ```
690
+
691
+ The hub stops when the terminal closes. To keep it running across reboots, see
692
+ [Keeping the hub running](#keeping-the-hub-running).
693
+
694
+ ---
695
+
696
+ ## Keeping the hub running
697
+
698
+ Everything above starts a hub in a terminal, and it stops when that terminal
699
+ closes. Nothing in this project has explained how to change that — while the
700
+ repository has shipped a systemd unit at its root for months and this page
701
+ refers to *"your service"* as though you already had one. That gap is the
702
+ reason this section exists.
703
+
704
+ DoSync does not implement supervision on any platform. It delegates: to systemd
705
+ on Linux, to the task scheduler on Windows. What differs between them is how
706
+ much you have to write, not whether it works.
707
+
708
+ **Say once what is at stake:** a hub that governs physical actions and does not
709
+ come back after a power cut is a deployment decision with consequences. If a
710
+ lock, an alarm or a machine depends on it, this is not optional.
711
+
712
+ ### Linux — systemd
713
+
714
+ `dosync.service` in the repository root is a template. Adjust the paths, then:
715
+
716
+ ```bash
717
+ sudo cp dosync.service /etc/systemd/system/
718
+ sudo systemctl daemon-reload
719
+ sudo systemctl enable --now dosync
720
+ systemctl status dosync
721
+ ```
722
+
723
+ **Keep secrets out of the unit.** It is a file people copy, and this one carried
724
+ a real Home Assistant token in a public repository until August 2026. Put them
725
+ in a drop-in that is not tracked:
726
+
727
+ ```bash
728
+ sudo install -d -m 700 /etc/systemd/system/dosync.service.d
729
+ sudo tee /etc/systemd/system/dosync.service.d/local.conf >/dev/null <<'EOF'
730
+ [Service]
731
+ Environment="HA_TOKEN=your-token-here"
732
+ EOF
733
+ sudo chmod 600 /etc/systemd/system/dosync.service.d/local.conf
734
+ sudo systemctl daemon-reload && sudo systemctl restart dosync
735
+ ```
736
+
737
+ systemd merges drop-ins over the unit, so the template never needs editing.
738
+
739
+ ### Windows — Task Scheduler
740
+
741
+ *Verified on Windows 11 ARM64 in a virtual machine, Python 3.14, installed with
742
+ `pipx`.*
743
+
744
+ Run PowerShell **as administrator**:
745
+
746
+ ```powershell
747
+ $exe = (Get-Command dosync-hub).Source
748
+ $db = "$env:USERPROFILE\.local\state\dosync\dosync.db"
749
+
750
+ $action = New-ScheduledTaskAction -Execute "cmd.exe" `
751
+ -Argument "/c set DOSYNC_DB=$db && `"$exe`""
752
+ $trigger = New-ScheduledTaskTrigger -AtStartup
753
+ $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" `
754
+ -LogonType ServiceAccount -RunLevel Highest
755
+ $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries `
756
+ -DontStopIfGoingOnBatteries -RestartCount 3 `
757
+ -RestartInterval (New-TimeSpan -Minutes 1)
758
+
759
+ Register-ScheduledTask -TaskName "DoSync Hub" -Action $action `
760
+ -Trigger $trigger -Principal $principal -Settings $settings
761
+ ```
762
+
763
+ `-AtStartup` with `SYSTEM` means the hub comes back after a reboot without
764
+ anyone logging in — the closest equivalent to systemd. `-RestartCount` covers a
765
+ process that dies.
766
+
767
+ **`DOSYNC_DB` is not optional, and leaving it out fails silently.** A scheduled
768
+ task does not inherit your environment, so a hub started this way writes to
769
+ `C:\Windows\System32\config\systemprofile\` — a different database from the
770
+ one you used by hand. It starts perfectly, reports `devices: 0`, and looks like
771
+ your inventory was lost. Setting `DOSYNC_DB` points both at the same file;
772
+ running it by hand afterwards reads what the scheduled task wrote, with no
773
+ permission conflict.
774
+
775
+ Check it:
776
+
777
+ ```powershell
778
+ curl.exe -s http://localhost:47200/v1/status | findstr db_path
779
+ Get-ScheduledTask -TaskName "DoSync Hub" | Get-ScheduledTaskInfo
780
+ ```
781
+
782
+ `LastTaskResult: 267009` is `0x41301` — *the task is currently running*. That is
783
+ the healthy value here; `0` would mean it exited.
784
+
785
+ **NSSM** installs the hub as a real Windows service and is what a server
786
+ deployment would use. It is third-party software this project does not ship or
787
+ test, so it is named rather than recommended.
788
+
789
+ ---
790
+
791
+ ## What's built today
792
+
793
+ | Component | Status |
794
+ |---|---|
795
+ | REST API (40+ endpoints) | ✅ |
796
+ | WebSocket real-time events | ✅ |
797
+ | Web dashboard | ✅ |
798
+ | API key authentication + SHA-256 audit log | ✅ |
799
+ | Capability-based resolver | ✅ |
800
+ | Certification CLI — Standard 33/33 · Emergency 44/44 (signed reports) | ✅ |
801
+ | Philips WiZ adapter (UDP local) | ✅ |
802
+ | Home Assistant bridge (10 domains) | ✅ |
803
+ | Native MCP server (Claude, ChatGPT, any LLM) | ✅ |
804
+ | GPIO adapter — Raspberry Pi 5 (PIR + DHT22) | ✅ |
805
+ | SMS notifications via Twilio | ✅ code (requires an active Twilio plan) |
806
+ | MQTT transport adapter (Mosquitto) | ✅ |
807
+ | Shelly adapter (HTTP local, Gen1 + Gen2) | ✅ code, not hardware-tested |
808
+ | Matter adapter (via HA bridge / python-matter-server) | ✅ code, not hardware-tested |
809
+ | External Resolver Protocol (HTTP wire format) | ✅ |
810
+ | SQLite persistence (survives restarts) | ✅ |
811
+ | CI pipeline (GitHub Actions) | ✅ |
812
+ | Multi-hub assisted failover (Phase A — operator-in-the-loop) | ✅ |
813
+ | Long-running operations + telemetry reconciliation (state machine) | ✅ |
814
+ | Drone / MAVLink adapter — full AI→intent→mission loop in ArduPilot SITL | ✅ software (physical flight pending) |
815
+
816
+ ---
817
+
818
+ ## MQTT transport
819
+
820
+ DoSync supports MQTT as a Layer 1 transport for devices that can't use HTTP. Requires Mosquitto and proper authentication. See [config/mosquitto-secure.conf](config/mosquitto-secure.conf) for secure setup.
821
+
822
+ ```bash
823
+ # Enable MQTT in the hub service
824
+ Environment="DOSYNC_MQTT_BROKER=localhost"
825
+ Environment="DOSYNC_MQTT_USER=dosync-hub"
826
+ Environment="DOSYNC_MQTT_PASSWORD=<password>"
827
+ Environment="DOSYNC_MQTT_SECRET=<registration-secret>"
828
+ ```
829
+
830
+ ---
831
+
832
+ ## Certification
833
+
834
+ Self-certifiable with the CLI:
835
+
836
+ ```bash
837
+ python3 certify.py --host <hub-ip> --port 47200 --tier standard
838
+ # Output: dosync-cert-standard-*.json
839
+ ```
840
+
841
+ | Tier | Tests | What it validates |
842
+ |---|---|---|
843
+ | **Basic** | 10 | Connectivity, auth, device manifest |
844
+ | **Standard** | 33 | Protocol conformance, events, health, version headers |
845
+ | **Emergency** | 44 | Everything in Standard + emergency override, policy engine, audit log integrity |
846
+
847
+ ---
848
+
849
+ ## Implementations
850
+
851
+ | Language | Location | Author | Certification |
852
+ |---|---|---|---|
853
+ | Python (reference) | `server.py` | this project | Standard 33/33 · Emergency 44/44 ✅ |
854
+ | 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 |
855
+
856
+ The Node.js implementation is a **companion** port that validates the protocol
857
+ is implementable in a second language against the same certification suite —
858
+ both share the same author. A genuinely **independent** implementation
859
+ (different author or organization) is a tracked milestone for v1.0: a protocol
860
+ needs multiple independent implementations to become a standard. See the
861
+ [vision and scope](docs/VISION.md).
862
+
863
+ ---
864
+
865
+ ## Works with Home Assistant — a layer on top, not a replacement
866
+
867
+ Home Assistant already solved the hardest problem: talking to thousands of devices, and since 2025 it ships an MCP server so an AI can control them directly. DoSync doesn't reinvent that — it reads devices from HA through a bridge already in the repo and adds **one thing**: it turns a semantic goal (`ensure_safety`, `away_mode`) into a coordinated, **auditable** set of actions across *any* source (HA, WiZ, GPIO, MQTT, BLE).
868
+
869
+ The honest version: for everyday automation ("porch light when I get home") you **don't need DoSync** — HA's automations and its MCP cover that completely. DoSync earns its place only when **coordination and traceability matter at once** — e.g. a fall-response that unlocks the door, lights the house, and messages family, with a tamper-evident record of exactly what fired and when. Full reasoning: [Home Assistant Already Talks to Your Devices. So What Would DoSync Add?](https://dev.to/giulianiregspec/home-assistant-already-talks-to-your-devices-so-what-would-dosync-add-1iei)
870
+
871
+ ---
872
+
873
+ ## Beyond the home
874
+
875
+ Nothing in DoSync assumes a house — the same 5-layer stack coordinates physical systems anywhere an AI needs to act: retail cold-chain, hotels, factory peripherals (alongside certified safety systems, never replacing them).
876
+
877
+ The proof: we took it to the hardest device, an **autonomous drone**. From a single plain-language sentence, an AI model (Claude Haiku, via DoSync's MCP server) fired an `inspect_area` intent and the drone flew the full mission in ArduPilot SITL — every step confirmed by real telemetry. When the AI guessed coordinates 11,000 km away, the supervisor didn't fake success: it waited for a confirmed arrival, none came, and it aborted with a clear diagnosis. **The AI can be wrong; the protocol doesn't have to be.** [Full build log](https://dev.to/giulianiregspec/i-gave-an-ai-one-sentence-a-drone-flew-the-mission-and-when-the-ai-guessed-wrong-the-system-2h3m) · *(validated in SITL; physical-hardware flight is the next step, not a claim made today.)*
878
+
879
+ ---
880
+
881
+ ## Contributing
882
+
883
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow, including the CI pipeline that runs on every push.
884
+
885
+ ---
886
+
887
+ ## Specification
888
+
889
+ - [spec/DoSync-SPEC-v0.1.md](spec/DoSync-SPEC-v0.1.md) — full protocol specification
890
+ - [spec/RESOLVER-SPEC-v0.3.md](spec/RESOLVER-SPEC-v0.3.md) — resolver interface + external resolver protocol
891
+ - [DESIGN-PRINCIPLES.md](DESIGN-PRINCIPLES.md) — architectural decisions and rationale
892
+ - [COMPATIBILITY.md](docs/COMPATIBILITY.md) — backward compatibility guarantees
893
+
894
+ ---
895
+
896
+ ## License
897
+
898
+ Apache 2.0 — free to implement, free to extend, no royalties.
899
+
900
+ ---
901
+
902
+ *DoSync Protocol · © 2026 Rodrigo Giuliani · rgiuliani@dosync.dev*