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.
- {dosync-0.6.3 → dosync-0.7.0}/PKG-INFO +57 -17
- {dosync-0.6.3 → dosync-0.7.0}/README.md +49 -13
- {dosync-0.6.3 → dosync-0.7.0}/dosync/__init__.py +2 -2
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapter_drafting.py +7 -2
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/__init__.py +12 -28
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/homeassistant.py +306 -61
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/matter.py +37 -28
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/mqtt.py +1 -1
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/notifications.py +9 -9
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/shelly.py +35 -26
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/wiz.py +58 -28
- dosync-0.7.0/dosync/audit.py +809 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/audit_backup.py +16 -12
- {dosync-0.6.3 → dosync-0.7.0}/dosync/auth.py +11 -11
- {dosync-0.6.3 → dosync-0.7.0}/dosync/certify.py +158 -4
- dosync-0.7.0/dosync/composite_executor.py +288 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/dashboard.html +25 -4
- {dosync-0.6.3 → dosync-0.7.0}/dosync/db.py +179 -66
- {dosync-0.6.3 → dosync-0.7.0}/dosync/declarative.py +53 -9
- {dosync-0.6.3 → dosync-0.7.0}/dosync/discovery.py +4 -4
- {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/3d-printer.yaml +2 -2
- {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/air-conditioner.yaml +2 -2
- {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/building-lighting.json +2 -1
- {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/industrial-conveyor.yaml +2 -2
- {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/light-generic.yaml +5 -4
- {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/declarative/television.yaml +2 -2
- dosync-0.7.0/dosync/execution.py +425 -0
- dosync-0.7.0/dosync/hub.py +999 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/manage.py +5 -5
- {dosync-0.6.3 → dosync-0.7.0}/dosync/mcp_server.py +245 -95
- {dosync-0.6.3 → dosync-0.7.0}/dosync/metrics.py +10 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/models.py +177 -96
- dosync-0.7.0/dosync/occupancy.py +105 -0
- dosync-0.7.0/dosync/plan_executor.py +347 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/policies.py +16 -23
- dosync-0.7.0/dosync/registry.py +167 -0
- dosync-0.7.0/dosync/resolvers.py +1176 -0
- dosync-0.7.0/dosync/restore.py +112 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/security.py +21 -21
- {dosync-0.6.3 → dosync-0.7.0}/dosync/server.py +343 -39
- dosync-0.7.0/dosync/spec/TAG-VOCABULARY.md +332 -0
- dosync-0.7.0/dosync/state_refresh.py +154 -0
- dosync-0.7.0/dosync/telemetry.py +99 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/templates/adapter-draft-prompt.md +8 -5
- {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/PKG-INFO +57 -17
- {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/SOURCES.txt +50 -1
- {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/requires.txt +2 -2
- {dosync-0.6.3 → dosync-0.7.0}/pyproject.toml +17 -4
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_adapter_drafting.py +29 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_adapters.py +8 -4
- dosync-0.7.0/tests/test_adapters_speak_the_protocol.py +102 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_audit_archive.py +49 -0
- dosync-0.7.0/tests/test_audit_chain_ordering.py +476 -0
- dosync-0.7.0/tests/test_audit_concurrency.py +82 -0
- dosync-0.7.0/tests/test_audit_endpoint_limit.py +40 -0
- dosync-0.7.0/tests/test_audit_hashless.py +40 -0
- dosync-0.7.0/tests/test_certification_guide.py +44 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_certification_honesty.py +50 -7
- dosync-0.7.0/tests/test_check_helpers_can_fail.py +31 -0
- dosync-0.7.0/tests/test_collaborators_read_the_live_hub.py +51 -0
- dosync-0.7.0/tests/test_composite_executor.py +31 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_composite_operations.py +6 -2
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_composite_orchestration.py +9 -5
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_composition_kind_db.py +6 -2
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_composition_kind_endpoint.py +6 -2
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_composition_routing.py +9 -5
- dosync-0.7.0/tests/test_dashboard_uses_live_endpoints.py +34 -0
- dosync-0.7.0/tests/test_db_write_serialization.py +72 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_declarative_adapters.py +16 -8
- dosync-0.7.0/tests/test_declarative_location.py +108 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_deployment_env_contract.py +2 -2
- dosync-0.7.0/tests/test_device_location.py +245 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_direct_action_governance.py +15 -6
- dosync-0.7.0/tests/test_direct_action_instrumented.py +86 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_explain_consistency.py +5 -1
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_explain_resolve_parity.py +54 -24
- dosync-0.7.0/tests/test_extraction_preserves_behaviour.py +635 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_geo.py +6 -2
- dosync-0.7.0/tests/test_ha_read_sensors.py +75 -0
- dosync-0.7.0/tests/test_health_recorded_once.py +82 -0
- dosync-0.7.0/tests/test_intent_rejections_visible.py +81 -0
- dosync-0.7.0/tests/test_location_restricts.py +165 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_channels.py +6 -2
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_return_home.py +6 -2
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_single_reader.py +6 -2
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_telemetry_closure.py +6 -2
- dosync-0.7.0/tests/test_mcp_dynamic_intents.py +267 -0
- dosync-0.7.0/tests/test_mcp_http_transport.py +60 -0
- dosync-0.7.0/tests/test_mcp_scenarios_are_live.py +78 -0
- dosync-0.7.0/tests/test_mcp_speaks_english.py +95 -0
- dosync-0.7.0/tests/test_mcp_status.py +27 -0
- dosync-0.7.0/tests/test_measurement_tools_are_honest.py +286 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_no_operator_data.py +235 -3
- dosync-0.7.0/tests/test_no_undefined_names.py +41 -0
- dosync-0.7.0/tests/test_occupancy.py +70 -0
- dosync-0.7.0/tests/test_openapi_contract.py +62 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_operation_guards.py +6 -2
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_operation_supervisor.py +6 -2
- dosync-0.7.0/tests/test_plan_executor.py +32 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_policies.py +7 -4
- dosync-0.7.0/tests/test_protocol_version.py +66 -0
- dosync-0.7.0/tests/test_registry.py +69 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_resolver_scoring.py +145 -1
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_route_composer.py +6 -2
- dosync-0.7.0/tests/test_schemas_match_the_hub.py +163 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_server.py +27 -7
- dosync-0.7.0/tests/test_server_enforces_auth.py +27 -0
- dosync-0.7.0/tests/test_state_refresh.py +50 -0
- dosync-0.7.0/tests/test_tag_vocabulary.py +72 -0
- dosync-0.7.0/tests/test_telemetry.py +47 -0
- dosync-0.7.0/tests/test_undefined_name_regressions.py +53 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_universal_intent_contract.py +17 -4
- dosync-0.7.0/tests/test_unknown_location_refused.py +57 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_wiring_audit.py +4 -42
- dosync-0.7.0/tests/test_wiz_closes_every_bulb.py +76 -0
- dosync-0.6.3/dosync/hub.py +0 -3711
- dosync-0.6.3/tests/test_mcp_dynamic_intents.py +0 -177
- {dosync-0.6.3 → dosync-0.7.0}/LICENSE +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/ble.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/declarative.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/adapters/mavlink.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/auth_fastapi.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/cert_signing.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/cli.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/composite_operations.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/config_reference.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/device_arbiter.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/discoverers.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/discoverers_mdns.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/discoverers_ssdp.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/ed25519_pure.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/examples/__init__.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/executor.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/geo.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/hub_monitor.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/lightweight.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/operation_guards.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/operation_supervisor.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/operations.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/paths.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/plugins.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/policy_config.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/py.typed +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/reconciler.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/route_composer.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/spec_coverage.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync/validation.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/dependency_links.txt +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/entry_points.txt +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/dosync.egg-info/top_level.txt +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/setup.cfg +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_audit_backup.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_audit_chain_integrity.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_audit_provenance.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_auth.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_ble_adapter.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_claim_state_machine.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_dashboard_tells_the_truth.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_db.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_declarative_quarantine.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_deployment_layout.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_device_health.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_device_heartbeat.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_device_identity.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_discovery_adoption.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_documentation_is_navigable.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_drone_policies.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_ed25519_pure.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_emergency_preemption.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_env_loading_is_explicit.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_evaluation_metrics.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_event_loop_migration.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_ha_bridge_hygiene.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_hub_monitor.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_idempotency.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_independent_observation.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_integration_suite.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_lightweight_heartbeat.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_adapter.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_mavlink_listener.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_mcp_partial_progress.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_metrics.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_models.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_multihub_endpoints.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_notification_templates.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_operations.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_operations_endpoints.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_operations_persistence.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_operations_wiring.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_panel_polish_2026_07_21.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_policy_config.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_public_claims.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_pushed_verification.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_quickstart_is_runnable.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_reachability_cause.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_recall_benchmark_postpolicy.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_reconciler.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_resolution_wiring.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_resolver_semantics.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_sensor_kind.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_simulation_is_declared.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_telemetry_bridge.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_third_party_adapters.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_transport_discoverers.py +0 -0
- {dosync-0.6.3 → dosync-0.7.0}/tests/test_validation.py +0 -0
- {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.
|
|
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,
|
|
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.
|
|
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.
|
|
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)
|
|
68
|
-
[](spec/DoSync-SPEC-v0.1.md)
|
|
69
73
|
[](https://pypi.org/project/dosync/)
|
|
70
74
|
[](https://pypi.org/project/dosync/)
|
|
71
75
|
[](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
|
|
216
|
+
User / AI says: "there is a fire in building B"
|
|
213
217
|
│
|
|
214
218
|
DoSync Hub
|
|
215
219
|
│
|
|
216
220
|
┌───────────────┼───────────────┐
|
|
217
221
|
▼ ▼ ▼
|
|
218
|
-
💡
|
|
219
|
-
at maximum
|
|
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.
|
|
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
|
-
|
|
813
|
-
and this server is written against 1.x; without the cap, a fresh
|
|
814
|
-
2.x and the server exits at import. It says so plainly if that
|
|
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
|
|
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
|
|
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 |
|
|
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
|
|
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)
|
|
6
|
-
[](spec/DoSync-SPEC-v0.1.md)
|
|
7
7
|
[](https://pypi.org/project/dosync/)
|
|
8
8
|
[](https://pypi.org/project/dosync/)
|
|
9
9
|
[](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
|
|
150
|
+
User / AI says: "there is a fire in building B"
|
|
151
151
|
│
|
|
152
152
|
DoSync Hub
|
|
153
153
|
│
|
|
154
154
|
┌───────────────┼───────────────┐
|
|
155
155
|
▼ ▼ ▼
|
|
156
|
-
💡
|
|
157
|
-
at maximum
|
|
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.
|
|
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
|
-
|
|
751
|
-
and this server is written against 1.x; without the cap, a fresh
|
|
752
|
-
2.x and the server exits at import. It says so plainly if that
|
|
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
|
|
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
|
|
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 |
|
|
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
|
|
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.
|
|
17
|
-
__protocol_version__ = "0.
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
#
|
|
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 —
|
|
177
|
+
# ── AdapterExecutor — the central executor ────────────────────────────────────
|
|
178
178
|
|
|
179
179
|
class AdapterExecutor:
|
|
180
180
|
"""
|
|
181
|
-
|
|
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
|
-
|
|
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:
|
|
199
|
-
fallback_to_simulated:
|
|
200
|
-
|
|
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
|
-
"""
|
|
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
|
|
273
|
-
|
|
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
|