dosync 0.4.1__tar.gz → 0.4.2__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.
- {dosync-0.4.1 → dosync-0.4.2}/PKG-INFO +329 -6
- dosync-0.4.2/README.md +634 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/__init__.py +1 -1
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/__init__.py +64 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/ble.py +61 -0
- dosync-0.4.2/dosync/adapters/declarative.py +241 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/notifications.py +14 -1
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/shelly.py +5 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/wiz.py +5 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/audit_backup.py +43 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/auth.py +44 -5
- {dosync-0.4.1 → dosync-0.4.2}/dosync/certify.py +116 -4
- dosync-0.4.2/dosync/config_reference.py +149 -0
- dosync-0.4.2/dosync/dashboard.html +1141 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/db.py +101 -0
- dosync-0.4.2/dosync/declarative.py +318 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/device_arbiter.py +54 -5
- dosync-0.4.2/dosync/examples/__init__.py +7 -0
- dosync-0.4.2/dosync/examples/declarative/3d-printer.yaml +50 -0
- dosync-0.4.2/dosync/examples/declarative/air-conditioner.yaml +47 -0
- dosync-0.4.2/dosync/examples/declarative/building-lighting.json +52 -0
- dosync-0.4.2/dosync/examples/declarative/industrial-conveyor.yaml +52 -0
- dosync-0.4.2/dosync/examples/declarative/light-generic.yaml +50 -0
- dosync-0.4.2/dosync/examples/declarative/television.yaml +40 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/hub.py +607 -12
- dosync-0.4.2/dosync/lightweight.py +156 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/manage.py +246 -1
- {dosync-0.4.1 → dosync-0.4.2}/dosync/models.py +41 -0
- dosync-0.4.2/dosync/plugins.py +106 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/server.py +732 -16
- dosync-0.4.2/dosync/spec_coverage.py +121 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync.egg-info/PKG-INFO +329 -6
- {dosync-0.4.1 → dosync-0.4.2}/dosync.egg-info/SOURCES.txt +23 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync.egg-info/requires.txt +6 -2
- {dosync-0.4.1 → dosync-0.4.2}/pyproject.toml +36 -3
- dosync-0.4.2/tests/test_audit_chain_integrity.py +981 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_auth.py +154 -0
- dosync-0.4.2/tests/test_certification_honesty.py +80 -0
- dosync-0.4.2/tests/test_declarative_adapters.py +305 -0
- dosync-0.4.2/tests/test_declarative_quarantine.py +196 -0
- dosync-0.4.2/tests/test_deployment_env_contract.py +570 -0
- dosync-0.4.2/tests/test_direct_action_governance.py +203 -0
- dosync-0.4.2/tests/test_discovery_adoption.py +380 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_emergency_preemption.py +123 -0
- dosync-0.4.2/tests/test_lightweight_heartbeat.py +238 -0
- dosync-0.4.2/tests/test_pushed_verification.py +182 -0
- dosync-0.4.2/tests/test_third_party_adapters.py +182 -0
- dosync-0.4.1/README.md +0 -315
- dosync-0.4.1/tests/test_deployment_env_contract.py +0 -115
- {dosync-0.4.1 → dosync-0.4.2}/LICENSE +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/homeassistant.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/matter.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/mavlink.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/adapters/mqtt.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/auth_fastapi.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/cert_signing.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/cli.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/composite_operations.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/discovery.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/ed25519_pure.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/executor.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/geo.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/hub_monitor.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/mcp_server.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/metrics.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/operation_guards.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/operation_supervisor.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/operations.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/policies.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/policy_config.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/py.typed +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/reconciler.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/route_composer.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/security.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync/validation.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync.egg-info/dependency_links.txt +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync.egg-info/entry_points.txt +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/dosync.egg-info/top_level.txt +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/setup.cfg +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_adapters.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_audit_archive.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_audit_backup.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_audit_provenance.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_ble_adapter.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_claim_state_machine.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_composite_operations.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_composite_orchestration.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_composition_kind_db.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_composition_kind_endpoint.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_composition_routing.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_db.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_device_health.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_device_heartbeat.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_drone_policies.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_ed25519_pure.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_event_loop_migration.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_explain_consistency.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_geo.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_ha_bridge_hygiene.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_hub_monitor.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_idempotency.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_independent_observation.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_integration_suite.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_mavlink_adapter.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_mavlink_channels.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_mavlink_listener.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_mavlink_return_home.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_mavlink_single_reader.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_mavlink_telemetry_closure.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_mcp_dynamic_intents.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_mcp_partial_progress.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_metrics.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_models.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_multihub_endpoints.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_operation_guards.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_operation_supervisor.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_operations.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_operations_endpoints.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_operations_persistence.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_operations_wiring.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_panel_polish_2026_07_21.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_policies.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_policy_config.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_reachability_cause.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_recall_benchmark_postpolicy.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_reconciler.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_resolution_wiring.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_resolver_scoring.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_resolver_semantics.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_route_composer.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_sensor_kind.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_server.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_telemetry_bridge.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_validation.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_validation_integration.py +0 -0
- {dosync-0.4.1 → dosync-0.4.2}/tests/test_wiring_audit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dosync
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
|
@@ -27,12 +27,16 @@ Requires-Dist: fastapi<1.0,>=0.115.0
|
|
|
27
27
|
Requires-Dist: uvicorn<1.0,>=0.23.0
|
|
28
28
|
Requires-Dist: pydantic<3.0,>=2.7.0
|
|
29
29
|
Requires-Dist: jsonschema<5.0,>=4.18
|
|
30
|
+
Requires-Dist: bleak>=0.21
|
|
31
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
32
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
33
|
+
Requires-Dist: paho-mqtt>=2.0.0
|
|
30
34
|
Provides-Extra: wiz
|
|
31
35
|
Requires-Dist: pywizlight>=0.5.0; extra == "wiz"
|
|
32
36
|
Provides-Extra: mqtt
|
|
33
37
|
Requires-Dist: paho-mqtt>=2.0.0; extra == "mqtt"
|
|
34
38
|
Provides-Extra: ha
|
|
35
|
-
Requires-Dist: aiohttp>=3.
|
|
39
|
+
Requires-Dist: aiohttp>=3.9.0; extra == "ha"
|
|
36
40
|
Provides-Extra: ble
|
|
37
41
|
Requires-Dist: bleak>=0.21; extra == "ble"
|
|
38
42
|
Provides-Extra: sms
|
|
@@ -44,7 +48,7 @@ Requires-Dist: mcp>=1.0.0; extra == "mcp"
|
|
|
44
48
|
Provides-Extra: all
|
|
45
49
|
Requires-Dist: pywizlight>=0.5.0; extra == "all"
|
|
46
50
|
Requires-Dist: paho-mqtt>=2.0.0; extra == "all"
|
|
47
|
-
Requires-Dist: aiohttp>=3.
|
|
51
|
+
Requires-Dist: aiohttp>=3.9.0; extra == "all"
|
|
48
52
|
Requires-Dist: bleak>=0.21; extra == "all"
|
|
49
53
|
Requires-Dist: twilio>=8.0.0; extra == "all"
|
|
50
54
|
Requires-Dist: pymavlink>=2.4.0; extra == "all"
|
|
@@ -57,7 +61,7 @@ Dynamic: license-file
|
|
|
57
61
|
|
|
58
62
|
# DoSync Protocol
|
|
59
63
|
|
|
60
|
-
>
|
|
64
|
+
> Governance and accountability for AI that acts on physical devices.
|
|
61
65
|
|
|
62
66
|
[](LICENSE)
|
|
63
67
|
[](spec/DoSync-SPEC-v0.1.md)
|
|
@@ -99,6 +103,65 @@ When the hub receives `"ensure_safety / emergency"`, every registered device fig
|
|
|
99
103
|
|
|
100
104
|
---
|
|
101
105
|
|
|
106
|
+
## How is this different from what already exists?
|
|
107
|
+
|
|
108
|
+
A fair question, and the honest answer is that DoSync sits **above** most of what
|
|
109
|
+
it gets compared to, not against it.
|
|
110
|
+
|
|
111
|
+
| | What it does | What it does not decide |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| **Matter, Zigbee, MQTT** | Move commands to devices | Which device should act, or whether it should |
|
|
114
|
+
| **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 |
|
|
115
|
+
| **MCP, A2A** | Connect an agent to tools | Anything about a tool being a door lock and the action being irreversible |
|
|
116
|
+
| **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 |
|
|
117
|
+
|
|
118
|
+
**Specifically on W3C Web of Things**, since it is the closest and the most
|
|
119
|
+
established: a Thing Description tells you a lock exposes a `lock` action and
|
|
120
|
+
how to invoke it. That is genuinely the right way to describe a device, and
|
|
121
|
+
DoSync does not compete with it. What a description cannot do is decide that a
|
|
122
|
+
lock is one of the things that should respond to *"there is an emergency"*,
|
|
123
|
+
refuse to touch it because this deployment forbids it, arbitrate when two
|
|
124
|
+
intents want it at once, or leave evidence afterwards that survives someone with
|
|
125
|
+
root access. Those are the questions DoSync answers.
|
|
126
|
+
|
|
127
|
+
**On MCP**: DoSync ships an MCP server. It is a distribution channel, not a
|
|
128
|
+
rival. MCP is how an agent reaches DoSync; DoSync is what happens between the
|
|
129
|
+
agent's goal and a device moving.
|
|
130
|
+
|
|
131
|
+
### The five things
|
|
132
|
+
|
|
133
|
+
Everything above reduces to five properties. Each is verifiable in a running
|
|
134
|
+
hub — the numbers below come from the reference deployment, not from a
|
|
135
|
+
brochure:
|
|
136
|
+
|
|
137
|
+
1. **Explainable resolution.** `GET /v1/intents/{class}/explain` returns which
|
|
138
|
+
devices were evaluated, which were included, and the score breakdown behind
|
|
139
|
+
each. The score it reports is the same value the resolver decided with — one
|
|
140
|
+
computation, not a narration of one.
|
|
141
|
+
2. **Policies the AI cannot route around.** A deployment declares what must not
|
|
142
|
+
happen; every path to a device is evaluated against it, including direct
|
|
143
|
+
device actions and the MCP tool. This was not true until we audited our own
|
|
144
|
+
claim and found the hole.
|
|
145
|
+
3. **A record that resists tampering — and says where it stops.** SHA-256 chain
|
|
146
|
+
with policy provenance, plus sequence numbers, a head mark and signed
|
|
147
|
+
exportable checkpoints. What it detects and what it cannot is written down in
|
|
148
|
+
[the threat model](docs/AUDIT-THREAT-MODEL.md), including the rows that read
|
|
149
|
+
"not detected".
|
|
150
|
+
4. **Formal arbitration of physical conflict.** A per-device claim state machine
|
|
151
|
+
with stated invariants ([consistency model §3.1](spec/CONSISTENCY-MODEL.md)), so an
|
|
152
|
+
emergency and a routine wanting the same device is resolved by rule rather
|
|
153
|
+
than by timing.
|
|
154
|
+
5. **Failure semantics that do not lie.** `contradicted` (the device said yes,
|
|
155
|
+
the sensor disagrees) is distinct from `unverifiable` (we could not look), and
|
|
156
|
+
`likely_powered_off` from `indeterminate`. The system says what it does not
|
|
157
|
+
know.
|
|
158
|
+
|
|
159
|
+
None of these is claimed to be unbreakable. Claim 3 in particular has documented
|
|
160
|
+
limits, on purpose: a protocol whose value is honesty cannot make absolute
|
|
161
|
+
security claims and stay coherent.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
102
165
|
## Scope and safety boundaries
|
|
103
166
|
|
|
104
167
|
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.
|
|
@@ -190,10 +253,43 @@ Benchmark (Raspberry Pi 5, Python 3.11.2):
|
|
|
190
253
|
### Install and run — five minutes, no hardware
|
|
191
254
|
|
|
192
255
|
```bash
|
|
193
|
-
|
|
256
|
+
pipx install dosync # recommended
|
|
194
257
|
dosync-hub
|
|
195
258
|
```
|
|
196
259
|
|
|
260
|
+
<details>
|
|
261
|
+
<summary><b>"error: externally-managed-environment"?</b> — Raspberry Pi OS, Debian 12+, Ubuntu 23.04+</summary>
|
|
262
|
+
|
|
263
|
+
Those systems refuse system-wide `pip install` (PEP 668) to stop Python packages
|
|
264
|
+
from breaking the OS. This hits the Raspberry Pi first, which is the most likely
|
|
265
|
+
machine to be running a hub, so it is worth getting right rather than working
|
|
266
|
+
around.
|
|
267
|
+
|
|
268
|
+
**`pipx` is the correct tool here** and not a workaround: DoSync is an
|
|
269
|
+
application with commands you run, not a library you import into your own code.
|
|
270
|
+
pipx gives it a private environment and still puts `dosync-hub`,
|
|
271
|
+
`dosync-manage` and `dosync-certify` on your PATH.
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
sudo apt install pipx # once
|
|
275
|
+
pipx ensurepath # once; open a new shell afterwards
|
|
276
|
+
pipx install dosync
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
If you are writing Python against DoSync rather than running the hub, a virtual
|
|
280
|
+
environment is the right choice instead:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
python3 -m venv ~/dosync-env
|
|
284
|
+
~/dosync-env/bin/pip install dosync
|
|
285
|
+
~/dosync-env/bin/dosync-hub
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`pip install --break-system-packages dosync` also works and is the one option we
|
|
289
|
+
would not recommend: it installs into the system Python that your OS depends on,
|
|
290
|
+
which is the situation PEP 668 exists to prevent.
|
|
291
|
+
</details>
|
|
292
|
+
|
|
197
293
|
That is a working hub on `http://127.0.0.1:47200`. It starts with a simulated
|
|
198
294
|
executor, so you can drive the whole protocol — register devices, fire intents,
|
|
199
295
|
read the audit chain — before you own a single smart device.
|
|
@@ -228,7 +324,93 @@ evaluated, which it included, and the score breakdown behind each decision.
|
|
|
228
324
|
Step 4 is the other: every action leaves a SHA-256-chained entry, so what the
|
|
229
325
|
system did is provable after the fact rather than merely logged.
|
|
230
326
|
|
|
231
|
-
|
|
327
|
+
**Discovery works out of the box.** The library that finds Bluetooth devices
|
|
328
|
+
ships in the core install, and the BLE adapter registers itself when it is
|
|
329
|
+
available — because discovery is how you learn what you have. Requiring an extra
|
|
330
|
+
first would be a circle: nobody installs a Bluetooth library before knowing they
|
|
331
|
+
own Bluetooth devices, and nobody can find out without it. A hub with no radio
|
|
332
|
+
loses nothing — the scan reports the transport as unsearchable rather than
|
|
333
|
+
failing — and `pip uninstall bleak` or `DOSYNC_BLE_ENABLED=false` removes it.
|
|
334
|
+
|
|
335
|
+
**On the adapters that ship with DoSync.** They come in three kinds, visible at
|
|
336
|
+
`GET /v1/adapters`. **Ecosystem** adapters implement open standards — MQTT,
|
|
337
|
+
Matter, BLE, MAVLink, and the Home Assistant bridge, which is the widest door of
|
|
338
|
+
all: anything HA already integrates, DoSync can reach. **Reference** adapters
|
|
339
|
+
(WiZ, Shelly) implement one vendor's product and ship as worked examples of how
|
|
340
|
+
an adapter is written — not as endorsement, partnership, or a promise to track
|
|
341
|
+
anyone's firmware. **Infrastructure** is notifications.
|
|
342
|
+
|
|
343
|
+
**If your device is not covered, describe it in a file.** A declarative adapter
|
|
344
|
+
is YAML or JSON — no code, no release of DoSync to wait for:
|
|
345
|
+
|
|
346
|
+
```yaml
|
|
347
|
+
device:
|
|
348
|
+
id: light-hallway
|
|
349
|
+
name: Hallway light
|
|
350
|
+
tags: [light, energy] # how intents find it
|
|
351
|
+
emergency_capable: true # whether an emergency may use it
|
|
352
|
+
|
|
353
|
+
transport:
|
|
354
|
+
kind: http
|
|
355
|
+
base_url: http://192.168.1.40
|
|
356
|
+
|
|
357
|
+
actions:
|
|
358
|
+
turn_on:
|
|
359
|
+
type: turn_on # what it MEANS to DoSync, not just its name
|
|
360
|
+
request: { method: POST, path: /light/on }
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Drop it in `declarative/` (or set `DOSYNC_DECLARATIVE_DIR`) and restart.
|
|
364
|
+
|
|
365
|
+
**You do not have to write the first one.** Six worked examples ship with the
|
|
366
|
+
package — a light, an air conditioner, a 3D printer, a television, a floor's
|
|
367
|
+
lighting controller and an industrial conveyor over MQTT — chosen so one of them
|
|
368
|
+
probably resembles what you have:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
dosync-manage examples # copies them into declarative/, ready to edit
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
They are also readable in the repository at
|
|
375
|
+
[`examples/declarative/`](examples/declarative/).
|
|
376
|
+
|
|
377
|
+
The `type` on each action is the part that matters. A file that only said "POST
|
|
378
|
+
/on turns it on" would let DoSync switch the device and leave it invisible to
|
|
379
|
+
everything else: no intent could select it, no policy could name it, an
|
|
380
|
+
emergency would pass it by.
|
|
381
|
+
|
|
382
|
+
**What a declarative adapter cannot do**, stated plainly: it speaks HTTP. It
|
|
383
|
+
cannot speak Zigbee, Z-Wave, BLE pairing, an OPC-UA session, or anything needing
|
|
384
|
+
a handshake, session state or a vendor SDK. Those need a code adapter — an
|
|
385
|
+
ecosystem one here, or a third-party package. This format covers most simple
|
|
386
|
+
devices and almost no complex ones.
|
|
387
|
+
|
|
388
|
+
**If it needs real code — pairing, a session, a vendor SDK — publish a package.**
|
|
389
|
+
DoSync discovers adapters advertised by anything installed alongside it:
|
|
390
|
+
|
|
391
|
+
```toml
|
|
392
|
+
# in the vendor's pyproject.toml
|
|
393
|
+
[project.entry-points."dosync.adapters"]
|
|
394
|
+
daikin = "dosync_adapter_daikin:DaikinAdapter"
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
The operator runs `pip install dosync-adapter-daikin` and the hub finds it. No
|
|
398
|
+
pull request here, and no promise from this project to maintain code for
|
|
399
|
+
hardware it has never seen — the publisher answers for their own adapter.
|
|
400
|
+
|
|
401
|
+
A third-party adapter runs inside the hub with the hub's permissions, so the hub
|
|
402
|
+
says so: it is logged at WARNING when loaded, recorded in the audit chain, and
|
|
403
|
+
reported as `kind: third_party` at `/v1/adapters` regardless of what the plugin
|
|
404
|
+
declares about itself. Where code came from is not the code's to assert.
|
|
405
|
+
|
|
406
|
+
DoSync does not download an adapter for you: the
|
|
407
|
+
protocol's whole argument is that nothing actuates hardware without a policy and
|
|
408
|
+
a record, and fetching executable code from the internet would put the largest
|
|
409
|
+
possible hole exactly there. Instead, an operator writes a declarative adapter
|
|
410
|
+
for HTTP/MQTT/Modbus devices, or installs a third-party package deliberately.
|
|
411
|
+
|
|
412
|
+
Install only the CONTROL adapters you need — those follow the opposite rule,
|
|
413
|
+
since you already know which hardware you own:
|
|
232
414
|
|
|
233
415
|
```bash
|
|
234
416
|
pip install 'dosync[wiz]' # Philips WiZ bulbs
|
|
@@ -237,6 +419,147 @@ pip install 'dosync[mqtt]' # MQTT devices
|
|
|
237
419
|
pip install 'dosync[all]' # everything
|
|
238
420
|
```
|
|
239
421
|
|
|
422
|
+
### Access: token, your own password, or none
|
|
423
|
+
|
|
424
|
+
The hub prints an API token the first time it starts and stores only a hash of
|
|
425
|
+
it, so it cannot show you that one again. Three ways to deal with that,
|
|
426
|
+
depending on who you are:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
# 1. Choose your own, like a password — for a person who has to type it
|
|
430
|
+
dosync-manage keys create --token "my-house-2026-kitchen" --label dashboard
|
|
431
|
+
|
|
432
|
+
# 2. Let it generate one — for a program that will store it
|
|
433
|
+
dosync-manage keys create --label my-integration
|
|
434
|
+
|
|
435
|
+
# 3. Turn authentication off entirely
|
|
436
|
+
DOSYNC_AUTH=false dosync-hub
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Option 3 is legitimate and not a trap door.** On a home network, behind a
|
|
440
|
+
router, with no port forwarding, requiring a token protects against nobody who
|
|
441
|
+
is not already inside your house. It is the wrong default for a clinic and a
|
|
442
|
+
reasonable choice for a workshop, so DoSync provides it plainly instead of
|
|
443
|
+
pretending everyone has the same threat model. What it is not suitable for is
|
|
444
|
+
any hub reachable from outside its own network.
|
|
445
|
+
|
|
446
|
+
Tokens are checked without rate limiting or lockout, so a chosen one must be at
|
|
447
|
+
least 12 characters and a passphrase of several words is better than a short
|
|
448
|
+
clever string. Existing keys: `dosync-manage keys list` (previews only — they
|
|
449
|
+
are hashed), `dosync-manage keys revoke <preview>`, `dosync-manage keys reset`.
|
|
450
|
+
|
|
451
|
+
### Access: a password you choose, or none at all
|
|
452
|
+
|
|
453
|
+
The hub prints a token on first start and stores only a hash of it, so it cannot
|
|
454
|
+
show you that one again. You are not stuck with it.
|
|
455
|
+
|
|
456
|
+
**From the dashboard** (the ⚙ button, once connected): set a password of your
|
|
457
|
+
choosing, or turn the token requirement off entirely. No shell, no unit file.
|
|
458
|
+
|
|
459
|
+
**From a terminal**, if you prefer:
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
# Choose your own — for a person who has to type it
|
|
463
|
+
dosync-manage keys create --token "my-house-2026-kitchen" --label dashboard
|
|
464
|
+
|
|
465
|
+
# Let it generate one — for a program that will store it
|
|
466
|
+
dosync-manage keys create --label my-integration
|
|
467
|
+
|
|
468
|
+
# Start with no authentication at all
|
|
469
|
+
DOSYNC_AUTH=false dosync-hub
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**Running without a token is a legitimate choice, not a trap door.** On a home
|
|
473
|
+
network, behind a router, with no port forwarding, a token protects against
|
|
474
|
+
nobody who is not already inside your house. It is the wrong default for a
|
|
475
|
+
clinic and an unnecessary obstacle for a workshop, so DoSync offers it plainly
|
|
476
|
+
rather than assuming everyone shares one threat model. It is not suitable for
|
|
477
|
+
any hub reachable from outside its own network.
|
|
478
|
+
|
|
479
|
+
Two things worth knowing:
|
|
480
|
+
|
|
481
|
+
- **`DOSYNC_AUTH` in the environment wins.** If it is set in your service
|
|
482
|
+
configuration, the dashboard will tell you so and refuse to override it — a
|
|
483
|
+
click in a browser should not quietly undo what the machine was told to do.
|
|
484
|
+
- **Changing access is recorded.** Setting a password or turning authentication
|
|
485
|
+
off appends to the audit chain, so "when did this hub become open, and who did
|
|
486
|
+
it" has an answer. The token value itself is never written there.
|
|
487
|
+
|
|
488
|
+
A chosen password must be at least 12 characters, and a passphrase of several
|
|
489
|
+
words is better than a short clever string: a bearer token is checked with no
|
|
490
|
+
rate limiting and no lockout, so it is guessed offline at full speed.
|
|
491
|
+
|
|
492
|
+
Existing keys: `dosync-manage keys list` (previews only — they are hashed),
|
|
493
|
+
`keys revoke <preview>`, `keys reset`.
|
|
494
|
+
|
|
495
|
+
### Hardware that cannot do TLS
|
|
496
|
+
|
|
497
|
+
A sensor running a year on a coin cell cannot perform a TLS handshake — it costs
|
|
498
|
+
more battery than a month of operation. Such a device can still report liveness,
|
|
499
|
+
signed rather than encrypted:
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
DOSYNC_LIGHTWEIGHT_HEARTBEAT=true dosync-hub
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
`POST /v1/heartbeat/signed` accepts a heartbeat authenticated by HMAC over the
|
|
506
|
+
device's provisioning token. It is **off by default**, and it is worth knowing
|
|
507
|
+
exactly what it trades before turning it on: the channel provides message
|
|
508
|
+
authenticity and replay resistance, and **no confidentiality** — the device id,
|
|
509
|
+
timestamp and report travel readable. Devices using it are marked
|
|
510
|
+
`report_channel: signed_plaintext` so they are distinguishable from ones on mTLS.
|
|
511
|
+
|
|
512
|
+
That trade is defensible for a heartbeat and would not be for an action: a
|
|
513
|
+
heartbeat is positive signal only, so a forged one cannot switch anything on.
|
|
514
|
+
The attack it invites is replay — repeating a captured message to keep a failed
|
|
515
|
+
device reporting healthy — and that is closed. See spec §7.10 and
|
|
516
|
+
[the threat model](docs/AUDIT-THREAT-MODEL.md).
|
|
517
|
+
|
|
518
|
+
### TLS, and why your browser says "Not secure"
|
|
519
|
+
|
|
520
|
+
`bash setup_pki.sh` creates a private certificate authority in `certs/` and
|
|
521
|
+
issues the hub a certificate from it. Start the hub with those files and traffic
|
|
522
|
+
is encrypted:
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
dosync-hub --host 0.0.0.0 & # or with uvicorn's --ssl-keyfile / --ssl-certfile
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
Your browser will then show **"Not secure"** with `https` struck through. This
|
|
529
|
+
is expected and it does **not** mean the connection is unencrypted. It means the
|
|
530
|
+
browser does not recognise the authority that signed the certificate — which is
|
|
531
|
+
you. A public CA cannot issue a certificate for `192.168.x.x`, so a hub on a
|
|
532
|
+
private network is always in this position.
|
|
533
|
+
|
|
534
|
+
Two honest options:
|
|
535
|
+
|
|
536
|
+
**Accept the warning.** Click through it. The connection is encrypted; what is
|
|
537
|
+
missing is a third party vouching that the server is who it claims. On your own
|
|
538
|
+
LAN, where you set up the hub yourself, that is a much smaller gap than it looks.
|
|
539
|
+
|
|
540
|
+
**Trust your own CA, and the warning goes away** — on the machines you choose:
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
# macOS
|
|
544
|
+
sudo security add-trusted-cert -d -r trustRoot \
|
|
545
|
+
-k /Library/Keychains/System.keychain certs/ca.crt
|
|
546
|
+
|
|
547
|
+
# Linux (Debian/Ubuntu)
|
|
548
|
+
sudo cp certs/ca.crt /usr/local/share/ca-certificates/dosync-ca.crt
|
|
549
|
+
sudo update-ca-certificates
|
|
550
|
+
|
|
551
|
+
# Windows (PowerShell, as Administrator)
|
|
552
|
+
Import-Certificate -FilePath ca.crt -CertStoreLocation Cert:\LocalMachine\Root
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Copy `certs/ca.crt` from the hub first — it is the only file you need, and it
|
|
556
|
+
contains no secret. The hub's private key (`certs/hub.key`) never leaves the
|
|
557
|
+
hub.
|
|
558
|
+
|
|
559
|
+
**What the warning does mean.** If you see it on a hub you did not set up, or on
|
|
560
|
+
a network you do not control, do not click through — that is exactly the case
|
|
561
|
+
the warning exists for.
|
|
562
|
+
|
|
240
563
|
### Docker
|
|
241
564
|
|
|
242
565
|
```bash
|