Uh oh!
There was an error while loading. Please reload this page.
schemas: DeviceService contract v0.1 — the southbound device plane (W8.7) - #215
Merged
Conversation
mdhellerforce-pushed
the
feat/device-service-contract
branch
2 times, most recently
from
July 29, 2026 21:50
94306a9 to
ba84bedCompare…8.7) The estate had no southbound device abstraction anywhere. The health twin ingests batch corpora; the smart home has nothing; a sub-50ms twin sync is aspirational for the simple reason that no sensor stream exists to be late. EdgeX Foundry's lesson is the fix, and it is a lesson about shape: ONE southbound interface, N protocol drivers. A driver speaks a protocol; it does not invent a vocabulary. The moment each driver defines its own event shape, every consumer downstream grows a per-driver special case and the "device plane" is a directory of adapters rather than a plane. Two types: DeviceProfile — what a device IS: protocol, typed protocol binding, and the exact metric set it produces with units, value types, operating ranges and protocol-native source addresses. DeviceReading — one observation, carried on the MPCC ConversationEvent envelope with byte-identical sub-schemas (parity machine-enforced). Normative invariant: A READING IS ATTRIBUTABLE OR IT IS NOTHING. deviceRef, deviceProfileRef, profileDigest, metric, sourceAddress and unit are all required — there is no optional path to an anonymous value. The validator resolves every one across the example set: the profile exists, the digest matches the RECOMPUTED declaration, the metric is declared, the unit and source address agree with it, the value is of the declared type and inside the declared range, and provenanceLinks independently name both the device and the profile — so the attribution survives being read by something that is not this validator. definitionDigest closes the retroactive-legalisation path: a reading fails the declared range, so somebody widens the range. With digest-pinning, widening produces a new digest and ORPHANS the readings it was meant to legalise instead of silently admitting them. Same construct as UpdateHealthProbe (W9.2), applied to metrology; recomputed, never read back. Quality is closed at five values, and unavailable is schema-bound to a null value plus a NullAbsenceRecord reference — the device plane reuses the existing 12-kind MPCC absence taxonomy rather than inventing a device-local one. Normatively, stale and substituted are not ok, and substituted is not measured: conflating them is how a smart-home twin comes to believe a default is a measurement, and then how a health twin does. protocol: "virtual" is a first-class, labelled member of the taxonomy, with the synthetic:simulated-device label enforced in both directions. This is the KnowledgeNugget model-generated rule applied to sensors: generated data that is indistinguishable downstream is worse than no data, because it is believed. observedAt / receivedAt / wallTime stay three fields because they measure three things; observedAt to receivedAt is the southbound latency a twin's sync budget is actually spent on, and collapsing them hides the only number that would say whether a target is met. access is closed to "read". Commanding a device is a world-changing effect and must travel EffectRequest -> EffectDecision; admitting actuation through a widened enum would hand every protocol driver unreviewed physical authority over a citizen's home. Overlap decided rather than ignored: DeviceIdentity governs admission and trust for an operator workstation and says nothing about metrology (DeviceProfile.identityRef binds to it; a negative vector forbids attributing a reading to one), and TelemetryEvent is an AgentSession diagnostic with no unit, range, quality or device. Neither is duplicated. Includes a BLE-GATT profile and the virtual profile the reference service actually runs, measured and typed-absence readings, 21 negative conformance vectors, a validate-device-service-examples target, and specs/ device-service-contract.md. Reference implementation: apps/device-service in SocioProphet/prophet-platform.
… check being laxer than the implementation it certifies
Adversarial pass on the contract's own conformance tool found two checks that
could not fail for the documents most likely to be wrong.
1. receivedAt >= observedAt was a STRING compare. `format: date-time` carries no
`pattern`, so schema-valid forms differ in offset and fractional precision.
Two proven counterexamples that the check MISSED:
observedAt 2026-07-29T09:15:00.500Z / receivedAt ...T09:15:00Z
-> 500ms earlier, admitted ('.' 0x2E < 'Z' 0x5A)
observedAt 2026-07-29T20:00:00+00:00 / receivedAt ...T21:00:00+05:00
-> 4h earlier, admitted
Both are now caught; equivalent instants written differently (Z vs +02:00,
lowercase t/z) still pass, so the fix has teeth in both directions. A pair
that cannot be parsed to an absolute instant now fails closed.
2. kkoTypeRef was only checked when BOTH sides carried one, so a reading that
simply omitted it passed — and recorded a green `ontology-agrees`. The
reference implementation (prophet-platform device-service contract.py) keys
the check off the PROFILE's declaration and rejects that document. A
conformance tool laxer than the implementation it certifies is worse than no
tool. Now aligned.
Also: `latency-ordered` no longer reports green on readings that carry no
receivedAt — a check that ran on nothing should not claim it passed.
specs §6 states the instants-not-strings rule normatively so an implementation
cannot reintroduce it and still claim conformance.
Schemas are unchanged and stay byte-identical to the vendored copies in
prophet-platform apps/device-service.mdhellerforce-pushed
the
feat/device-service-contract
branch
from
July 30, 2026 01:24
ba84bed to
bd1fb43CompareUh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
The estate has no southbound device abstraction anywhere. The health twin ingests batch corpora; the smart home has nothing; a Δt<50ms twin sync is aspirational for the simple reason that no sensor stream exists to be late.
EdgeX Foundry's lesson is the fix, and it is a lesson about shape, not about adopting EdgeX: ONE southbound interface, N protocol drivers. A driver speaks a protocol; it does not invent a vocabulary. The moment each driver defines its own event shape, every consumer downstream grows a per-driver special case and the "device plane" is a directory of adapters rather than a plane.
Two types
DeviceProfileurn:srcos:device-profile:DeviceReadingobservedAt— on the MPCCConversationEventenvelope, byte-identical, parity machine-enforced.urn:srcos:device-reading:The normative invariant: a reading is attributable or it is nothing
deviceRef,deviceProfileRef,profileDigest,metric,sourceAddressandunitare all required — there is no optional path to an anonymous value.tools/validate_device_service_examples.pyresolves every one across the example set: the profile exists, the digest matches the recomputed declaration, the metric is declared, the unit and source address agree with it, the value is of the declared type and inside the declared range, andprovenanceLinksindependently name both the device and the profile — so the attribution survives being read by something that is not this validator.Digest-pinning: a range cannot be widened after the fact
A reading fails the declared range, so somebody widens the range.
DeviceProfile.definitionDigest(sha256 over the{deviceClass, protocol, metrics}projection as written) closes that: widening produces a new digest and orphans the readings it was meant to legalise instead of silently admitting them. Same construct asUpdateHealthProbe(W9.2), applied to metrology — and recomputed, never read back. A digest that is merely stored is an assertion about pinning; a digest that is recomputed is pinning.Quality is closed, absence is typed, simulation is visible
ok/degraded/stale/substituted/unavailable. Normativelystaleandsubstitutedare notok, andsubstitutedis not measured — conflating them is how a smart-home twin comes to believe a default is a measurement, and then how a health twin does.unavailableis schema-bound (if/then/else) to a null value plus aNullAbsenceRecordref. The device plane reuses the existing 12-kind MPCC absence taxonomy rather than inventing a device-local one.protocol: "virtual"is a first-class labelled member of the taxonomy; the validator enforcessynthetic:simulated-devicein both directions. This is theKnowledgeNuggetmodel-generatedrule applied to sensors: generated data that is indistinguishable downstream is worse than no data, because it is believed.Read-only at v0.1
accessis closed to"read". Commanding a device is a world-changing effect and must travelEffectRequest→EffectDecision. Admitting actuation through a widened enum would hand every protocol driver unreviewed physical authority over a citizen's home.Overlap decided, not ignored
DeviceIdentityDeviceProfile.identityRefbinds to it. A negative vector forbids attributing a reading to aurn:srcos:device-identity:URN.TelemetryEventAgentSessiondiagnostic log event — no unit, range, quality or device. Untouched.NullAbsenceRecordConversationEventTopic/Offer/WorkOrder/…)Verification
fixtures/device-service/conformance.json— every one derived from a complete canonical example and broken in exactly one way, so a failure is attributable to the named defect. Verified all 21 fail, none passes.receivedAtbeforeobservedAt) each turn it red. A checker that cannot go red is theatre.Deliberate gaps (§11 of the spec note)
Deviceinstance registry. The profile describes a device model;deviceRefnames an instance by aurn:srcos:device:URN with no schema of its own yet. Same house convention asConversationEvent's free-formactorRef/workspaceRef/branchRef. First follow-on.DeviceReadingwould launder a computation into a measurement).Reference implementation:
device-serviceinSocioProphet/prophet-platform(separate PR).