feat(vesc): integrate native calibration diagnostics and configuration archives

This commit is contained in:
DCCONSTRUCTIONS
2026-09-25 16:37:56 +03:00
parent 24bbaefb00
commit 45fb14b206
85 changed files with 17968 additions and 28 deletions
+19
View File
@@ -84,6 +84,25 @@ def cert(row):
serialization.Encoding.DER
)
def test_rover_stream_requires_current_pairing_and_binding(setup):
fleet, _, _, _ = setup
public, _ = create(setup)
fleet.advance(public["id"])
row = fleet.find(public["id"])
body = {"schema": SCHEMA, "node_id": row["node_id"],
"binding_id": row["binding"]["binding_id"], "relay_id": "a"*32}
certificate = cert(row)
status, result = fleet.receive(certificate, "/v1/node/rover-stream", body)
assert status == 200
assert result['command'] is None
assert result['control_clock']['instance'] == fleet.rover_control.clock_id
status, _ = fleet.receive(certificate, "/v1/node/rover-stream", {**body, "binding_id": "other"})
assert status == 410
row['enrollment'] = 'revoked'
fleet.save(row)
status, _ = fleet.receive(certificate, "/v1/node/rover-stream", body)
assert status == 410
def test_migrated_heartbeat_preserves_identity_and_old_reply_cannot_reverse(setup):
fleet, _, _, _ = setup
+74
View File
@@ -0,0 +1,74 @@
import base64
import copy
import hashlib
from datetime import UTC, datetime
from uuid import uuid4
import pytest
from k1link.device_plugins.vesc.archive import Archive
def backup():
uid = "01" * 12
configs = {}
for name, command in (("motor", 14), ("application", 17)):
raw = bytes([command]) + b"test-synthetic-config"
configs[name] = {"encoding": "base64", "payload": base64.b64encode(raw).decode(),
"bytes": len(raw), "sha256": hashlib.sha256(raw).hexdigest(),
"signature_hex": raw[1:5].hex()}
return {"schema": "missioncore.vesc.config-backup/v1", "operation_id": "op_" + uuid4().hex,
"device_id": "vesc_" + hashlib.sha256(("uuid:" + uid).encode()).hexdigest()[:32],
"identity": {"uuid": uid, "version": "6.06"}, "configs": configs,
"observed_at": datetime.now(UTC).isoformat()}
def test_immutable_versions_survive_restart_and_do_not_cross_node_scope(tmp_path):
archive = Archive(tmp_path / "archive")
value = backup()
archive.add("node-a", value)
archive.add("node-a", copy.deepcopy(value))
reopened = Archive(tmp_path / "archive")
assert len(reopened.listing("node-a", value["device_id"])["items"]) == 1
assert reopened.read("node-a", value["device_id"], value["operation_id"]) == value
assert reopened.listing("node-b", value["device_id"])["items"] == []
with pytest.raises(ValueError):
reopened.read("node-b", value["device_id"], value["operation_id"])
changed = copy.deepcopy(value)
changed["observed_at"] = datetime.now(UTC).isoformat()
with pytest.raises(ValueError, match="immutable"):
reopened.add("node-a", changed)
def test_corrupt_or_misattributed_payload_is_never_archived(tmp_path):
archive = Archive(tmp_path / "archive")
value = backup()
value["configs"]["motor"]["sha256"] = "0" * 64
with pytest.raises(ValueError, match="integrity"):
archive.add("a", value)
value = backup()
value["device_id"] = "vesc_" + "0" * 32
with pytest.raises(ValueError, match="mismatch"):
archive.add("a", value)
assert archive.export("a", 0)["items"] == []
def test_bounded_replication_replays_without_losing_versions_and_paginates(tmp_path):
board, core = Archive(tmp_path / "board"), Archive(tmp_path / "core")
values = [backup() for _ in range(53)]
for value in values:
board.add("local", value)
after = 0
while True:
batch = board.export("local", after)
assert len(batch["items"]) <= 4
assert core.receive("paired-node", batch) == batch["next"]
assert core.receive("paired-node", batch) == batch["next"]
if batch["next"] == after:
break
after = batch["next"]
page = core.listing("paired-node", values[0]["device_id"])
assert len(page["items"]) == 50 and page["next"]
older = core.listing("paired-node", values[0]["device_id"], int(page["next"]))
assert len(older["items"]) == 3 and older["next"] is None
assert len({item["id"] for item in page["items"] + older["items"]}) == 53