feat(plugin-sdk): add executable v0alpha2 contracts

This commit is contained in:
DCCONSTRUCTIONS
2026-07-16 19:43:26 +03:00
parent 27bf7527df
commit 63dd8c8790
18 changed files with 1634 additions and 26 deletions
+364
View File
@@ -0,0 +1,364 @@
from __future__ import annotations
import base64
import hashlib
import json
from datetime import UTC, datetime, timedelta
from pathlib import Path
import pytest
from missioncore_plugin_sdk import v0alpha2 as sdk
from pydantic import TypeAdapter, ValidationError
NOW = datetime(2026, 7, 16, 12, 0, tzinfo=UTC)
def _model() -> sdk.DeviceModelRef:
return sdk.DeviceModelRef(
plugin_id="nodedc.device.synthetic",
plugin_version="0.2.0",
model_id="synthetic.sensor",
)
def _device() -> sdk.DeviceInstanceRef:
return sdk.DeviceInstanceRef(
device_id="synthetic.device-001",
model=_model(),
stability=sdk.IdentityStability.STABLE,
basis=sdk.IdentityBasis.PLUGIN_DERIVED,
)
def _session_ref() -> sdk.DeviceSessionRef:
return sdk.DeviceSessionRef(
session_id="session-001",
device_id="synthetic.device-001",
)
def _header() -> sdk.CanonicalStreamHeader:
return sdk.CanonicalStreamHeader(
message_id="message-001",
session=_session_ref(),
acquisition_id="acquisition-001",
source_id="lidar-primary",
channel_id="points",
sequence=1,
captured_at=NOW,
observed_at=NOW + timedelta(milliseconds=3),
clock_domain=sdk.ClockDomain.UTC,
compatibility_profile_id="synthetic.fw-1.v1",
)
def _inline_payload(data: bytes) -> sdk.InlinePayload:
return sdk.InlinePayload(
media_type="application/x-pointcloud-struct",
byte_length=len(data),
sha256=f"sha256:{hashlib.sha256(data).hexdigest()}",
data=base64.b64encode(data).decode("ascii"),
)
def test_plugin_sdk_v0alpha2_identity_separates_transport_alias_from_stable_id() -> None:
alias = sdk.TransportAlias(
transport="ble.corebluetooth",
scope_id="agent.macbook-01",
value="SYNTHETIC-K1-0001",
observed_at=NOW,
)
provisional = sdk.DeviceInstanceRef(
device_id="observation.ble-synthetic-k1-0001",
model=_model(),
stability=sdk.IdentityStability.PROVISIONAL,
basis=sdk.IdentityBasis.TRANSPORT_LOCAL,
aliases=(alias,),
)
assert provisional.aliases == (alias,)
with pytest.raises(ValidationError, match="transport-local identifier"):
sdk.DeviceInstanceRef(
device_id="incorrectly-stable",
model=_model(),
stability=sdk.IdentityStability.STABLE,
basis=sdk.IdentityBasis.TRANSPORT_LOCAL,
)
def test_plugin_sdk_v0alpha2_session_tracks_three_independent_state_machines() -> None:
context = sdk.DeviceSessionContext(
session_id="session-001",
device=_device(),
execution=sdk.ExecutionBinding(
node_id="mission-node.local-01",
agent_instance_id="agent-process-001",
platform=sdk.ExecutionPlatform.MACOS,
),
compatibility_profile_id="synthetic.fw-1.v1",
opened_at=NOW,
)
snapshot = sdk.DeviceSessionSnapshot(
context=context,
revision=4,
enrollment=sdk.EnrollmentState.ENROLLED,
connectivity=sdk.ConnectivityState.CONNECTED,
acquisition=sdk.AcquisitionState.IDLE,
observed_at=NOW,
active_operation_ids=("operation-health",),
)
assert snapshot.enrollment is sdk.EnrollmentState.ENROLLED
assert snapshot.connectivity is sdk.ConnectivityState.CONNECTED
assert snapshot.acquisition is sdk.AcquisitionState.IDLE
with pytest.raises(ValidationError, match="active operation ids must be unique"):
sdk.DeviceSessionSnapshot.model_validate(
{
**snapshot.model_dump(),
"active_operation_ids": ("operation-health", "operation-health"),
}
)
def test_plugin_sdk_v0alpha2_operation_ack_is_not_completion() -> None:
policy = sdk.OperationPolicy(
action_id="acquisition.start",
safety_class=sdk.OperationSafetyClass.STATE_CHANGING,
idempotency=sdk.IdempotencyMode.IDEMPOTENT_WITH_KEY,
timeout_seconds=30,
retry_limit=1,
preconditions=("device.connected", "calibration.ready"),
secret_fields=("device_credential",),
)
request = sdk.OperationRequest(
operation_id="operation-start-001",
session=_session_ref(),
action_id="acquisition.start",
requested_at=NOW,
deadline_at=NOW + timedelta(seconds=30),
idempotency_key="acquisition.start:001",
parameters={"project_id": "project-001"},
secret_refs={
"device_credential": sdk.SecretReference(
provider="missioncore.secret-store",
reference="devices/synthetic-001/credential",
)
},
)
sdk.validate_operation_request(policy, request)
adapter = TypeAdapter(sdk.OperationEvent)
acknowledgement = adapter.validate_python(
{
"event_type": "acknowledgement",
"operation_id": request.operation_id,
"session": request.session.model_dump(),
"sequence": 0,
"recorded_at": NOW,
"disposition": "accepted",
}
)
completion = adapter.validate_python(
{
"event_type": "completion",
"operation_id": request.operation_id,
"session": request.session.model_dump(),
"sequence": 2,
"recorded_at": NOW + timedelta(seconds=1),
"result": {"acquisition_id": "acquisition-001"},
}
)
assert isinstance(acknowledgement, sdk.OperationAcknowledgement)
assert isinstance(completion, sdk.OperationCompletion)
assert acknowledgement.event_type != completion.event_type
def test_plugin_sdk_v0alpha2_operation_policy_fails_closed() -> None:
with pytest.raises(ValidationError, match="automatic retries"):
sdk.OperationPolicy(
action_id="calibration.start",
safety_class=sdk.OperationSafetyClass.SAFETY_CRITICAL,
idempotency=sdk.IdempotencyMode.NON_IDEMPOTENT,
timeout_seconds=60,
retry_limit=1,
)
policy = sdk.OperationPolicy(
action_id="camera.catalog.read",
safety_class=sdk.OperationSafetyClass.READ_ONLY,
idempotency=sdk.IdempotencyMode.IDEMPOTENT,
timeout_seconds=5,
)
request = sdk.OperationRequest(
operation_id="operation-camera-001",
session=_session_ref(),
action_id="camera.preview.start",
requested_at=NOW,
deadline_at=NOW + timedelta(seconds=5),
)
with pytest.raises(ValueError, match="action_id"):
sdk.validate_operation_request(policy, request)
def test_plugin_sdk_v0alpha2_point_cloud_layout_and_payload_are_verified() -> None:
payload = _inline_payload(b"\0" * 24)
frame = sdk.PointCloudFrame(
header=_header(),
coordinate_frame="sensor.lidar-primary",
width=2,
point_count=2,
point_step=12,
row_step=24,
dense=False,
fields=(
sdk.PointField(name="x", offset=0, data_type=sdk.PointFieldType.FLOAT32),
sdk.PointField(name="y", offset=4, data_type=sdk.PointFieldType.FLOAT32),
sdk.PointField(name="z", offset=8, data_type=sdk.PointFieldType.FLOAT32),
),
payload=payload,
)
assert frame.payload.byte_length == frame.point_count * frame.point_step
with pytest.raises(ValidationError, match="point_count"):
sdk.PointCloudFrame.model_validate({**frame.model_dump(), "point_count": 3})
with pytest.raises(ValidationError, match="sha256"):
sdk.InlinePayload.model_validate({**payload.model_dump(), "sha256": f"sha256:{'0' * 64}"})
def test_plugin_sdk_v0alpha2_evidence_is_immutable_and_lineage_is_explicit() -> None:
handle = sdk.EvidenceHandle(
evidence_id="evidence.raw-001",
store_id="evidence-store.local",
media_type="application/vnd.tcpdump.pcap",
byte_length=1024,
sha256=f"sha256:{'a' * 64}",
object_key="experiments/2026-07-16/raw-001.pcap",
)
record = sdk.EvidenceRecord(
handle=handle,
plugin_id="nodedc.device.synthetic",
session=_session_ref(),
created_at=NOW,
source_kind="packet-capture",
retention=sdk.EvidenceRetention.EXPERIMENT,
redaction=sdk.RedactionState.UNREVIEWED,
)
assert record.handle.sha256.startswith("sha256:")
with pytest.raises(ValidationError, match="cannot name itself"):
sdk.EvidenceRecord.model_validate(
{**record.model_dump(), "parent_evidence_ids": [handle.evidence_id]}
)
with pytest.raises(ValidationError, match="frozen"):
record.redaction = sdk.RedactionState.CLEARED # type: ignore[misc]
def test_plugin_sdk_v0alpha2_contracts_are_deeply_immutable() -> None:
source_parameters = {
"pipeline": {
"stages": ["capture", "decode"],
"options": {"raw_first": True},
}
}
request = sdk.OperationRequest(
operation_id="operation-immutable-001",
session=_session_ref(),
action_id="acquisition.prepare",
requested_at=NOW,
deadline_at=NOW + timedelta(seconds=30),
parameters=source_parameters,
)
pipeline = request.parameters["pipeline"]
assert isinstance(pipeline, dict)
stages = pipeline["stages"]
assert isinstance(stages, list)
options = pipeline["options"]
assert isinstance(options, dict)
with pytest.raises(TypeError, match="deeply immutable"):
request.parameters["new"] = True
with pytest.raises(TypeError, match="deeply immutable"):
options["raw_first"] = False
with pytest.raises(TypeError, match="deeply immutable"):
stages.append("publish")
source_parameters["pipeline"]["stages"].append("outside-mutation")
assert stages == ["capture", "decode"]
wire_document = request.model_dump(mode="json")
assert wire_document["parameters"] == {
"pipeline": {
"stages": ["capture", "decode"],
"options": {"raw_first": True},
}
}
assert json.loads(json.dumps(wire_document))["parameters"] == wire_document["parameters"]
assert "parameters" in sdk.OperationRequest.model_json_schema()["properties"]
copy_update = {"parameters": {"pipeline": {"stages": ["publish"]}}}
copied = request.model_copy(update=copy_update)
copied_stages = copied.parameters["pipeline"]["stages"]
assert copied_stages == ["publish"]
with pytest.raises(TypeError, match="deeply immutable"):
copied_stages.append("mutate")
copy_update["parameters"]["pipeline"]["stages"].append("outside-mutation")
assert copied_stages == ["publish"]
with pytest.raises(ValidationError, match="extra"):
request.model_copy(update={"undeclared": True})
def test_plugin_sdk_v0alpha2_compatibility_blocks_unproven_control() -> None:
pass_rule = sdk.CompatibilityRuleResult(
rule_id="firmware.exact-match",
outcome=sdk.RuleOutcome.PASS,
blocking=True,
detail="Firmware and protocol fixture match the reviewed profile.",
)
assessment = sdk.CompatibilityAssessment(
assessment_id="compatibility-001",
device=_device(),
evaluated_at=NOW,
observed_firmware="1.0.0",
profile_id="synthetic.fw-1.v1",
decision=sdk.CompatibilityDecision.COMPATIBLE,
permitted_mode=sdk.PermittedMode.ACTIVE_CONTROL,
rules=(pass_rule,),
supported_actions=("acquisition.start", "acquisition.stop"),
)
assert assessment.permitted_mode is sdk.PermittedMode.ACTIVE_CONTROL
with pytest.raises(ValidationError, match="active control requires a compatible decision"):
sdk.CompatibilityAssessment.model_validate(
{
**assessment.model_dump(),
"decision": "unknown",
"permitted_mode": "active-control",
"profile_id": None,
}
)
def test_plugin_sdk_v0alpha2_exports_closed_json_schemas_and_vocabulary() -> None:
schemas = sdk.contract_json_schemas()
assert {
"DeviceInstanceRef",
"DeviceSessionSnapshot",
"OperationEvent",
"CanonicalStreamEnvelope",
"EvidenceRecord",
"CompatibilityAssessment",
}.issubset(schemas)
assert "oneOf" in schemas["OperationEvent"]
with pytest.raises(ValidationError, match="extra"):
sdk.DeviceSessionRef.model_validate(
{"session_id": "session-001", "device_id": "device-001", "vendor": "forbidden"}
)
repository_root = Path(__file__).resolve().parents[1]
vocabulary = (
repository_root / "docs/domain-model/mission-core-experimental-vocabulary-v0alpha2.md"
).read_text(encoding="utf-8")
assert "acknowledgement is not operation completion" in vocabulary
assert "not yet a NODE.DC platform ontology" in vocabulary