RFC 9880

Semantic Definition Format (SDF) for Data and Interactions of Things

πŸ“… published January 2026, from ietf

RFC 9880: Semantic Definition Format (SDF) for Data and Interactions of Things πŸ’–

  • RFC number: 9880
  • Title: Semantic Definition Format (SDF) for Data and Interactions of Things
  • Authors: M. Koster, Ed. (KTC Control AB), C. Bormann, Ed. (UniversitΓ€t Bremen TZI), A. KerΓ€nen (Ericsson)
  • Category: Standards Track
  • Date: January 2026
  • ISSN: 2070-1721
  • Obsoletes / Updates: this header doesn't list anything it obsoletes or updates πŸŽ€

Abstract ✨

okay so SDF (the Semantic Definition Format) is a format that domain experts use to build and maintain data and interaction models for Things, which are physical objects you can interact with over a network πŸ¦‹ An SDF spec describes SDF Objects and SDF Things, plus their interactions (Events, Actions, and Properties), and the Data types for the info that gets exchanged in those interactions πŸ’… Tools can then convert the format into database formats and other serializations whenever needed 🫢

Status of This Memo πŸ“œ

This is an Internet Standards Track document. It's a product of the Internet Engineering Task Force (IETF), so it represents the consensus of the IETF community. It got public review and was approved for publication by the Internet Engineering Steering Group (IESG) πŸ‘‘ For more on Internet Standards, check out Section 2 of RFC 7841.

Want the current status, any errata, or a way to send feedback? Head to https://www.rfc-editor.org/info/rfc9880 🌸

Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they're in effect on the date this document was published. Please actually read these, because they spell out your rights and restrictions for this document πŸ™ˆ Code Components pulled out of this document must include Revised BSD License text, as described in Section 4.e of the Trust Legal Provisions, and they come without warranty, as described in the Revised BSD License ✨

Table of Contents πŸ—ΊοΈ

   1.  Introduction
     1.1.  Structure of This Document
     1.2.  Terminology and Conventions
       1.2.1.  Programming Platform Terms
       1.2.2.  Conceptual Terms
       1.2.3.  Specification Language Terms
       1.2.4.  Conventions
   2.  Overview
     2.1.  Example Definition
     2.2.  Elements of an SDF Model
       2.2.1.  sdfObject
       2.2.2.  sdfProperty
       2.2.3.  sdfAction
       2.2.4.  sdfEvent
       2.2.5.  sdfData
       2.2.6.  sdfThing
     2.3.  Member Names: Given Names and Quality Names
       2.3.1.  Given Names and Quality Names
       2.3.2.  Hierarchical Names
       2.3.3.  Extensibility of Given Names and Quality Names
   3.  SDF Structure
     3.1.  Information Block
     3.2.  Namespaces Block
     3.3.  Definitions Block
     3.4.  Top-Level Affordances and sdfData
   4.  Names and Namespaces
     4.1.  Structure
     4.2.  Contributing Global Names
     4.3.  Referencing Global Names
     4.4.  sdfRef
       4.4.1.  Resolved Models
     4.5.  sdfRequired
     4.6.  Common Qualities
     4.7.  Data Qualities
       4.7.1.  sdfType
       4.7.2.  sdfChoice
   5.  Keywords for Definition Groups
     5.1.  sdfObject
     5.2.  sdfProperty
     5.3.  sdfAction
     5.4.  sdfEvent
     5.5.  sdfData
   6.  High-Level Composition
     6.1.  Paths in the Model Namespaces
     6.2.  Modular Composition
       6.2.1.  Use of the "sdfRef" Keyword to Reuse a Definition
     6.3.  sdfThing
   7.  IANA Considerations
     7.1.  Media Type
     7.2.  Content-Format
     7.3.  IETF URN Sub-Namespace for Unit Names
           (urn:ietf:params:unit)
     7.4.  SenML Registry Group
     7.5.  Registries
       7.5.1.  SDF Quality Names
       7.5.2.  SDF Quality Name Prefixes
       7.5.3.  sdfType Values
       7.5.4.  SDF Feature Names
   8.  Security Considerations
   9.  References
     9.1.  Normative References
     9.2.  Informative References
   Appendix A.  Formal Syntax of SDF
   Appendix B.  json-schema.org Rendition of SDF Syntax
   Appendix C.  Data Qualities Inspired by json-schema.org
     C.1.  type "number", type "integer"
     C.2.  type "string"
     C.3.  type "boolean"
     C.4.  type "array"
     C.5.  type "object"
     C.6.  Implementation Notes
   Appendix D.  Composition Examples
     D.1.  Outlet Strip Example
     D.2.  Refrigerator-Freezer Example
   Appendix E.  Some Changes from Earlier Draft Versions of this
           Specification
   List of Figures
   List of Tables
   Acknowledgements
   Contributors
   Authors' Addresses

1. Introduction ✨

SDF is all about Things, which are physical objects that can be interacted with over a network πŸ¦‹ It's a format that domain experts use to create and maintain the data and interaction models that describe those Things. An SDF spec describes SDF Objects/SDF Things and their interactions (Events, Actions, and Properties), plus the Data types for the info exchanged in those interactions. And yep, tools convert it into database formats and other serializations as needed πŸ’–

SDF is designed to be extensible, which is very much my favorite kind of design 🧚 This document is the base spec for SDF, or "base SDF" for short. SDF extensions can also be defined, and some of them may use extension points that base SDF defines specifically for this. One area for extensions is refining SDF's abstract interaction models into protocol bindings for specific ecosystems (e.g., [SDF-MAPPING]). Some other extensions may need the SDF document using them to indicate that a specific extension is in effect. See Section 3.1 for the features quality you can use for those indications πŸŽ€ With extension points and feature indications in place, base SDF doesn't define a "version" concept for the SDF format itself. (Documents can still show their own evolution through version indications, see Section 3.1, so it's not all chaos πŸ’…)

1.1. Structure of This Document πŸ—‚οΈ

Okay, so after the intro and the overview (Section 2), which walks through the model's elements and the different kinds of names used, Section 3 covers the main components of an SDF model. Section 4 revisits names and sorts them into namespaces. Section 5 gets into the inner structure of the Objects SDF defines, the sdfObjects 🌈 Section 6 explains how SDF supports composition. Then come the Conventional Sections (IANA Considerations, Security Considerations, Normative References, and Informative References) πŸ’œ

The normative Appendix A defines SDF's syntax in terms of its JSON structures, using the Concise Data Definition Language (CDDL) [RFC8610]. Next is the informative Appendix B, a rendition of the SDF syntax in a "JSON Schema" format as defined by json-schema.org (collectively called JSO) ✨ The normative Appendix C defines certain terms ("data qualities") used at the SDF data model level that were inspired by JSO. The informative Appendix D gives a few examples of composition in SDF πŸ“ Last but not least, Appendix E has historical info that's useful for upgrading earlier, pre-standard SDF models and implementations to base SDF πŸ¦‹

1.2. Terminology and Conventions πŸ“š

Terms introduced in this section get capitalized when they show up in this section. To keep things readable, the body of the document only capitalizes them where it's actually needed πŸ’…

1.2.1. Programming Platform Terms πŸ’»

These definitions cover terms that programming platforms use with their own specific meanings. They often have an independent definition for this document, which you'll find further down in this section 🫢

  • Element: A generic term, used in its plain English sense. The one exception is Appendix C, where it's used explicitly according to its meaning in the JSON ecosystem, meaning the elements of JSON arrays.
  • Entry: A key-value pair in a map. (In JSON maps, this is sometimes called a "member".)
  • Map: A collection of entries (key-value pairs) where no two entries have equivalent keys. (Also known as an associative array, dictionary, or symbol table.)
  • Object: A super generic term that JavaScript (and therefore JSON) uses for the kind of maps that were there from the start. In this document, Object is only used in its general English meaning, or as the casual shorthand for sdfObject. That's true even when the type name "object" gets imported with JSON-related semantics from a data definition language 😭
  • Property: Some environments use "property" for a JSON concept that JSON calls "member" and this document calls "entry", or sometimes just for the map key of those. In this document, the term Property is reserved specifically for one kind of Affordance, even if the map key "properties" gets imported with JSON-related semantics from a data definition language πŸ’œ
  • Byte: This document uses "byte" in its now-customary sense, as a synonym for "octet" 🌸

1.2.2. Conceptual Terms 🧠

  • Thing: A physical item that's also available for interaction over a network 🌈
  • Element: A part or aspect of something abstract. Here, the term is used in its usual English definition πŸ’…
  • Affordance: An element of an interface that's offered for interaction. An element becomes an Affordance once information is available (directly or indirectly) that says how it can or should be used. This document uses the term only for the digital, network-directed interfaces of a Thing. Since a Thing is also a physical object, it might have physical affordances too, like buttons, dials, and displays πŸ”₯ The specification language offers certain ways to create sets of related Affordances and combine them into "Groupings" (see below) ✨
  • Property: An Affordance that can potentially be used to read, write, and/or observe state (current or stored information) on a Grouping πŸ¦‹
  • Action: An Affordance that can potentially be used to perform a named operation on a Grouping πŸ‘‘
  • Event: An Affordance that can potentially be used to get information about what happened to a Grouping πŸŽ€

1.2.3. Specification Language Terms

πŸ’– SDF Document: the container for SDF Definitions, plus data about the SDF Document itself (the information block). It's represented as a JSON text representing a single JSON map, which is built from nested maps. πŸŽ€

SDF Model: the definitions and declarations that model the digital interaction opportunities offered by one or more kinds of Things. They're represented by Groupings (sdfObjects and sdfThings). An SDF Model can be fully contained in a single SDF Document, or it can be built from an SDF Document that references definitions and declarations from additional SDF documents. You can say SDF Specification when you don't care about how the model is split into individual SDF Documents and just want the abstract model. 🧚

Block: one or more entries in a JSON map that's part of an SDF specification. We call them a Block when we want to highlight that they work together for one specific job. 🧩

Group: an entry in the top-level JSON map that represents the SDF document. Groups can also show up in certain nested definitions. A group has a Class Name Keyword as its key and a map of named definition entries (Definition Group) as its value. πŸ“¦

Class Name Keyword: one of sdfThing, sdfObject, sdfProperty, sdfAction, sdfEvent, or sdfData. The Classes for these type keywords are capitalized and prefixed with sdf. 🏷️

Class: the abstract term for the information that's contained in groups identified by a Class Name Keyword. πŸ’…

Quality: a metadata item in a definition or declaration that says something about that definition or declaration. In SDF, a quality is an entry in a JSON map (JSON object) that serves as a definition or declaration. (We say "Quality" because another popular term, "Property", already means something different. πŸ™ˆ)

Definition: an entry in a Definition Group. The entry creates a new semantic term for use in SDF models and associates it with a set of qualities. Unless the Class Name Keyword of the Group also makes it a Declaration (see Section 3.3), a definition just defines a term, and it does not create a component item within the enclosing definition. πŸ“

Declaration: a definition within an enclosing definition that's meant to create a component item within that enclosing definition. Every declaration can also be used as a definition for reference elsewhere. πŸ¦‹

Grouping: an sdfThing or sdfObject, i.e., (directly or indirectly) a description for a combination of Affordances. 🌈

Object, sdfObject: a Grouping where the declarations it contains are for Affordances only (Property, Action, and Event declarations). It's the main "atom" of reusable semantics for model construction, representing the interaction model for a Thing that's simple enough not to need a nested structure. So sdfObjects are similar to sdfThings, but they don't allow nesting, i.e., they cannot contain other Groupings (sdfObjects or sdfThings). It's giving minimal but iconic. ✨

sdfThing: a Grouping that can contain nested Groupings (sdfThings and sdfObjects). Like sdfObject, it can also contain Affordance declarations (Property, Action, and Event declarations). (Heads up: "Thing" means something different from sdfThing, so it's not an okay colloquial shorthand for sdfThing. πŸ™…β€β™€οΈ)

Augmentation Mechanism: a companion document to a base SDF Model that provides additional information ("augments" the base specification). The information may be for use in a specific ecosystem or with a specific protocol ("Protocol Binding"). No specific Augmentation Mechanisms are defined in base SDF. A simple mechanism for such augmentations has been discussed as a "mapping file" [SDF-MAPPING]. πŸ—ΊοΈ

Protocol Binding: a companion document to an SDF Model that defines how to map the abstract concepts in the model into the protocols that are in use in a specific ecosystem. The Protocol Binding might supply URL components, numeric IDs, and similar details. Protocol Bindings are one case of an Augmentation Mechanism. πŸ”—

1.2.4. Conventions

Regular expressions used in the text as a "pattern" for some string are interpreted as per [RFC9485]. (Note that a form of regular expressions is also used as values of the quality pattern; see Appendix C.2. 🧐)

The term "URI" in this document always refers to "full" URIs ("URI" in Section 3 of RFC 3986 [STD66]). It never refers to relative URI references ("relative-ref" in Section 4.1 of RFC 3986 [STD66]). So the term "URI" does not serve as the colloquial abbreviation of "URI-Reference" that people often use it for. Therefore, the "reference resolution" process defined in Section 5 of RFC 3986 [STD66] is not used in this specification. Where necessary, full URIs are assembled out of substrings by simple concatenation, e.g., when CURIEs are expanded (Section 4.3) or when a global name is formed out of a namespace absolute-URI (Section 5 of RFC 3986 [STD66]) and a fragment identifier part (Section 4.1). 🧡

Also, URIs aren't only used to construct the SDF models. They're also the subject of SDF models, where they're used as data in actual interactions (and could even be represented as relative references there). These two usages are totally separate, so keep them apart in your head. πŸ’œ

The singular form is chosen as the preferred one for the keywords defined in this specification. ☝️

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. 🎯

2. Overview

2.1. Example Definition

The overview starts with an example of the SDF definition for a simple sdfObject called "Switch" (Figure 1). Let's go! πŸ¦‹

{
  "info": {
    "title": "Example document for SDF (Semantic Definition Format)",
    "version": "2019-04-24",
    "copyright": "Copyright 2019 Example Corp. All rights reserved.",
    "license": "https://example.com/license"
  },
  "namespace": {
    "cap": "https://example.com/capability/cap"
  },
  "defaultNamespace": "cap",
  "sdfObject": {
    "Switch": {
      "sdfProperty": {
        "value": {
          "description":
"The state of the switch; false for off and true for on.",
          "type": "boolean"
        }
      },
      "sdfAction": {
        "on": {
          "description":
"Turn the switch on; equivalent to setting value to true."
        },
        "off": {
          "description":
"Turn the switch off; equivalent to setting value to false."
        },
        "toggle": {
          "description":
"Toggle the switch; equivalent to setting value to its complement."
        }
      }
    }
  }
}

            Figure 1: A Simple Example of an SDF Document

Okay, so this is a model of a switch. ⚑ The state value declared in the sdfProperty group is a Boolean, so it'll be true for "on" and false for "off". The Actions on and off in the sdfAction group are redundant with setting the value. They're in the example to show that there are often different ways of getting the same effect. πŸ™ƒ The action toggle inverts the value of the sdfProperty value, so you can make 2-way switches. Having that action means you don't have to retrieve the current value first and then apply the inverted value. Galaxy brain move. 🧠✨

The sdfObject group lists the affordances of Things modeled by this sdfObject. The sdfProperty group lists the Property affordances described by the model, and these represent different perspectives on the state of the sdfObject. Properties can have additional qualities to describe the state more precisely. Properties can be annotated to be read, write, or read/write. How that actually gets done by the underlying transfer protocols isn't described in the SDF model. It's left to companion protocol bindings. 🫢 Properties are often used with RESTful paradigms [REST-IOT] to describe state. The sdfAction group is the mechanism for describing other interactions in terms of their names, input, and output data (no data are used in the example), like a POST method in REST or a remote procedure call. The toggle example is an Action that changes the state based on the current state of the Property named value. (The third type of affordance is Events, which this example doesn't describe. πŸ‘€)

In the JSON representation, the info group is an exception, because its map has keys taken from the SDF vocabulary. All other groups (like namespace and sdfObject) have maps with keys that the model writer freely defines (Switch, value, on, etc.). These map keys are therefore called Given Names. The groups made up of entries with Given Names as keys usually use the named<> production in the formal syntax of SDF (Appendix A). When the values of these entries are maps, those maps use SDF vocabulary keys, and so on, generally alternating in further nesting. The SDF-defined vocabulary items used in the hierarchy of such groups are often, but not always, called Quality Names or qualities. See Section 2.3 for more information about naming in SDF. 🏷️

2.2. Elements of an SDF Model

The SDF language uses six predefined Class Name Keywords for modeling connected Things. They're illustrated in Figure 2 (this is a limited rendition in the plaintext form of this document, so please use typographic forms for full information πŸ’…).

                              ,--------.
                              |sdfThing|------.
               ,--------------|--------|      | hasThing
               |              |--------|<-----'
               |              `--------'
               |                |   | |
               |      hasObject |   |  \
               |                v   |   \
               |        ,---------. |    \
               |        |sdfObject| |     \
               |        |---------| |      \
               ,--------|---------|---------.
               |        `---------' |       |
            has|Property     | hasAction    | hasEvent
               v             v      v       v
            ,-----------.  ,---------.   ,--------.
            |sdfProperty|  |sdfAction|   |sdfEvent|
            |-----------|  |---------|   |--------|
            |-----------|  |---------|   |--------|
            `-----------'  `---------'   `--------'
               |      hasInput|   |hasOutput    |
               |          Data|   |Data         |
               |              v   v             |
               |            ,-------.           |
               | isInst     |sdfData|  hasOutp  |
               `----------->|-------|<----------'
                 anceOf     |-------|  utData
                            `-------'

              Figure 2: Main Classes Used in SDF Models

The six main Class Name Keywords are discussed below. Stay tuned, bestie! 🌸

2.2.1. sdfObject ✨

okay bestie, sdfObjects (the items listed in an sdfObject definition group) are the main "atom" of reusable semantics when you're building a model πŸ§©πŸ’–. Their scope lines up with the common definition items in lots of IoT modeling systems, like ZigBee Clusters [ZCL], OMA SpecWorks LwM2M Objects [OMA], OCF Resource Types [OCF], and W3C Web of Things Thing Descriptions [WoT] 🌸.

An sdfObject definition holds a set of sdfProperty, sdfAction, and sdfEvent definitions. Together they describe the interaction affordances for some scope of functionality πŸŽ€.

The granularity thing is so important πŸ™ˆ. sdfObject definitions should stay narrow enough that they can be reused everywhere, which is what makes them interoperable. For example, if you define a light bulb using separate sdfObject definitions for on/off control, dimming, and color control, you get interoperable functionality that can be configured for all sorts of product types. A common on/off control sdfObject can be used to control tons of different kinds of Things that need on/off control. It's giving modular, it's giving reusable πŸ‘‘.

The presence of one or both of the optional qualities minItems and maxItems makes the sdfObject an array 🧁. That means all the affordances defined for the sdfObject exist a number of times, indexed by a number that's constrained to be between minItems and maxItems, inclusive, if they're given. (Note: setting minItems to zero and leaving out maxItems puts the minimum constraints on that array. Lowkey a very specific flex πŸ’….)

2.2.2. sdfProperty πŸ¦‹

sdfProperty is used to model elements of state within Things modeled by the enclosing grouping πŸ’œ.

A named definition entry in an sdfProperty may be associated with some protocol affordance, so the application can obtain the state variable and, optionally, modify it. Some protocols also provide for in-time reporting of state changes πŸ”₯. (These three aspects are described by the qualities readable, writable, and observable, which are defined for an sdfProperty.)

Definitions in sdfProperty groups look like the definitions in sdfData groups. But they actually declare that a Property with the given qualities potentially is present in the containing sdfObject 🫢.

For definitions in sdfProperty and sdfData, SDF gives you qualities that can constrain the structure and values of the data allowed in the interactions they model. It also gives you qualities that attach semantics to that data, like engineering units and unit scaling information πŸ“βœ¨.

For the data definition within sdfProperty or sdfData, SDF borrows some vocabulary proposed for drafts 4 [JSO4] [JSO4V] and 7 [JSO7] [JSO7V] of the json-schema.org "JSON Schema" format. We're calling those collectively JSO here, and they've been enhanced with qualities specific to SDF 🌈. Details about the JSO-inspired vocabulary are in Appendix C. For base SDF, data are constrained to be of simple types (number, string, boolean), JSON maps composed of named data, and arrays of these types. There are syntax extension points for richer types in a future extension of this specification, and maybe more of those can be borrowed from json-schema.org πŸ“.

Note that sdfProperty definitions (and sdfData definitions in general) are not meant to constrain the formats of data used for communication over network interfaces πŸ™…β€β™€οΈ. Where needed, data definitions for payloads of protocol messages are expected to be part of the protocol binding. Not the model, bestie!

2.2.3. sdfAction πŸ’₯

The sdfAction group contains declarations of Actions. These model affordances that, when triggered, have an effect that can go beyond just reading, updating, or observing Thing state πŸš€. Actions often cause some outward physical effect, which SDF itself can't model. From a programmer's perspective, they're kind of like method calls 🧚.

Actions may have data parameters, and each one is modeled as a single item of input data and output data. When you need to model multiple parameters, you can use an "object" type to combine them into one. For an example, see Figure 6 in Appendix C.5 πŸ“¦.

Actions may be long-running. The effects might not happen right away the way they would for an update to an sdfProperty. They can play out over time and emit action results ⏳. Actions also might not always complete, and they can result in application errors, like an item blocking the closing of an automatic door πŸšͺπŸ’€.

One idiom for giving an action initiator status and control over an ongoing action is to provide a URI for an ephemeral "action resource" in the sdfAction output data 🎟️. That way, the action can deliver immediate feedback (including errors that stop the action from starting), and the initiator can use the action resource for further observation or modification of the ongoing action, including canceling it. Base SDF doesn't give any tailored support for describing action resources. An extension for modeling links in more detail (for instance, [SDFTYPE-LINK]) may be all that's needed to fully enable modeling them πŸ”—.

Actions may have (or lack) the characteristics of idempotence and side-effect safety. See Section 9.2 of RFC 9110 [STD97] for more on these terms πŸ€“.

Base SDF only provides data constraint modeling and semantics for the input and output data of definitions in sdfAction groups. Again, data definitions for payloads of protocol messages, and detailed protocol settings for invoking the action, are expected to be part of the protocol binding 🫠.

2.2.4. sdfEvent πŸ“£

The sdfEvent group contains declarations of Events. These model affordances that tell you about "happenings" associated with a Thing modeled by the enclosing sdfObject πŸ‘€. They may result in a signal being stored or emitted.

There's a trivial overlap with sdfProperty state changes, which can also be defined as Events but aren't generally required to be πŸ€·β€β™€οΈ. However, Events may show certain ordering, consistency, and reliability requirements. Those are expected to be supported in various implementations of sdfEvent that do distinguish sdfEvent from sdfProperty 🧠. For instance, a state change may simply be superseded by another state change, but some Events are "precious" and need to be preserved even if more Events follow πŸ’Ž.

Base SDF only provides data constraint modeling and semantics for the output data of Event affordances. Again, data definitions for payloads of protocol messages, and detailed protocol settings for soliciting the event, are expected to be part of the protocol binding πŸ’….

2.2.5. sdfData 🧩

Definitions in sdfData groups don't specify affordances themselves πŸ™ƒ. They're provided separately from the ones in sdfProperty groups so that common modeling patterns, data constraints, and semantic anchor concepts can be factored out. This applies to data items that make up sdfProperty items and that serve as input and output data for sdfAction and sdfEvent items 🎨. The data types defined in sdfData definitions only spring to life when they're referenced in one of these contexts, either directly or indirectly through other sdfData definitions ✨.

It's a common use case for one data definition to be shared between an sdfProperty item and the input or output parameters of an sdfAction, or the output data provided by an sdfEvent 🀝. sdfData definitions also let you factor out extended application data types, like mode and machine state enumerations, so they can be reused across multiple definitions with similar basic characteristics and requirements 🌟.

2.2.6. sdfThing 🏠

Back at the top level, the sdfThing group lets you define models for complex devices that use one or more sdfObject definitions πŸ€–. Like sdfObject, sdfThing groups allow the inclusion of interaction affordances and sdfData, as well as the "minItems" and "maxItems" qualities. So they can be seen as a superset of sdfObject groups, with the extra bonus of allowing composition 🧱.

As a result, an sdfThing directly or indirectly contains a set of sdfProperty, sdfAction, and sdfEvent definitions. Those describe the interaction affordances associated with some scope of functionality 🌈.

A definition in an sdfThing group can refine the metadata of the definitions it's composed of. Those can be other definitions in sdfThing groups or definitions in sdfObject groups πŸ’œ.

2.3. Member Names: Given Names and Quality Names 🏷️

Okay, this part is the naming system and I'm obsessed 😭. SDF documents are JSON maps that mostly use JSON maps as member values, which in turn mostly use JSON maps as their member values, and so on πŸ’–. This nested structure creates a tree, where the edges are the member names (the map keys) used in these JSON maps. In certain cases, where member names aren't needed, JSON arrays may be mixed into this tree 🌳.

2.3.1. Given Names and Quality Names πŸŽ€

For any particular JSON map in an SDF document, the set of member names used is either:

  • A set of "Quality Names", where the entries in the map are Qualities. Quality Names are defined by this specification and its extensions, together with specific semantics to be associated with the member value given with a certain Quality Name πŸ’….
  • A set of "Given Names", where the entries in the map are separate entities (definitions, declarations, etc.) that each have names chosen by the SDF document author, so that a user of that model can use those names 🧁.

On a path from the root of the tree to any leaf, Quality Names and Given Names roughly alternate (with the information block, Section 3.1, as a prominent exception) πŸ”.

The meaning of the JSON map that's the member value associated with a Given Name is derived from the Quality Name used as the member name for the parent. In the CDDL grammar given in Appendix A, JSON maps with member names that are Given Names are defined using the CDDL generic rule reference named<membervalues>. Here, membervalues is the structure of the member values of the JSON map, meaning the value of the member named by the Given Name 🧠. Since quality-named maps and given-named maps roughly alternate down the tree, membervalues is usually a map built from Quality Names as keys πŸ—οΈ.

2.3.2. Hierarchical Names 🌐

From outside a specification, Given Names are usually used as part of a hierarchical name that looks like a JSON Pointer [RFC6901]. These hierarchical names are generally rooted in (used as the fragment identifier in) an outer namespace that looks like an https:// URL. See Section 4 πŸ”—.

Since Quality Names and Given Names roughly alternate as you go into the model, the JSON Pointer part of the hierarchical name also alternates between Quality Names and Given Names πŸ”„.

Heads up, the actual Given Names may need to be encoded when they're specified with the JSON Pointer fragment identifier syntax πŸ™ˆ. There are two layers of encoding. First, there's tilde encoding of ~ and / as per Section 3 of [RFC6901]. Then there's percent encoding of the tilde-encoded name into a valid URI fragment, as per Section 6 of [RFC6901] 🫧. For example, when a model uses the Given Name

warning/danger alarm

(with an embedded slash and a space) for an sdfObject, that sdfObject may need to be referenced as

#/sdfObject/warning~1danger%20alarm
To sidestep potential interoperability problems, it is probably wise to avoid characters in Given Names that need such encoding. Quality Names are already defined in such a way that they never do 🌸✨

2.3.3. Extensibility of Given Names and Quality Names ✨

okay so in SDF, both Quality Names AND Given Names are extension points 🧚 this is more obvious for Quality Names tho. Extending SDF is mostly done by defining additional qualities πŸ’… To let non-conflicting third party extensions happen, qualified names (names with an embedded colon) can be used as Quality Names πŸŽ€

A nonqualified Quality Name is made of ASCII letters, digits, and $ signs, and it starts with a lowercase letter or a $ sign (so the pattern is "[a-z$][A-Za-z$0-9]*") πŸ¦‹ Names with $ signs are meant for functions that are kinda separate from most other names. For example, $comment is used for the comment quality in this specification (and having or not having a $comment quality doesn't change the meaning of the SDF model πŸ™ˆ). Names made of multiple English words can use the lowerCamelCase convention [CamelCase] to show word boundaries. No other upper case letters are intended in Quality Names πŸ”₯

A qualified Quality Name is made of a Quality Name Prefix, a : (colon) character, and a nonqualified Quality Name. Quality Name Prefixes are registered in the "Quality Name Prefixes" registry in the "Semantic Definition Format (SDF)" registry group (Section 7.5.2) 🌸 They're made of lowercase ASCII letters and digits, starting with a lowercase ASCII letter (so the pattern is "[a-z][a-z0-9]*").

Given Names aren't restricted by the formal SDF syntax 🫢 To make name translations in tools non-surprising, combinations of ASCII alphanumeric characters and - (ASCII hyphen/minus) are preferred, typically in kebab-case for names made of multiple words [KebabCase]. ASCII hyphen/minus can then unambiguously be translated to an ASCII _ (underscore) and back, depending on the programming environment. Some styles also allow a dot (".") in Given Names. Given Names are often self-explanatory enough to be used in place of the label quality if that isn't given. And if a Given Name turns out too complicated, you can give a more elaborate label and keep the Given Name simple πŸ’– Since Given Names are "programmers' names," base SDF doesn't address internationalization of Given Names. (The qualities more likely to get localizable equivalents by using the Quality Name extension point are label and description.)

Further, to let Given Names play a more powerful role in building global hierarchical names, an extension is foreseen that makes use of qualified names for Given Names. So, until that extension is defined, Given Names with one or more embedded colons are reserved and MUST NOT be used in an SDF document 🚫

All names in SDF are case-sensitive ✨


3. SDF Structure πŸ—οΈ

SDF definitions are contained in SDF documents, together with data about the SDF document itself (the information block) πŸ“¦ Definitions and declarations from additional SDF documents can be referenced. Together with the definitions and declarations in the referencing SDF document, they build the SDF model expressed by that SDF document 🧩

Each SDF document is represented as a single JSON map. You can think of this map as having three blocks: the information block, the namespaces block, and the definitions block. These blocks contain zero or more JSON name/value pairs, the names of which are Quality Names and the values of which mostly are (nested) maps. (The exception defined in base SDF is the defaultNamespace quality, the value of which is a text string.) An empty nested map of this kind is equivalent to not having the quality included at all 🌈

3.1. Information Block

The information block contains generic metadata for the SDF document itself and all included definitions πŸ“ For tool integration, the information block is optional in the grammar of SDF. But most processes for working with SDF documents will have policies that only SDF documents with an info block can be processed. So it is RECOMMENDED that SDF validator tools emit a warning when no information block is found ⚠️

The keyword (map key) that defines an information block is "info". The keyword's value is a nested JSON map with a set of entries that represent qualities that apply to the included definitions πŸŽ€

The qualities of this map are shown in Table 1. None of these qualities are required, and none have default values that are assumed if the quality is absent πŸ’…

    +=============+==========+=================================+
    | Quality     | Type     | Description                     |
    +=============+==========+=================================+
    | title       | string   | A short summary to be displayed |
    |             |          | in search results, etc.         |
    +-------------+----------+---------------------------------+
    | description | string   | Long-form text description (no  |
    |             |          | constraints)                    |
    +-------------+----------+---------------------------------+
    | version     | string   | The incremental version of the  |
    |             |          | definition                      |
    +-------------+----------+---------------------------------+
    | modified    | string   | Time of the latest modification |
    +-------------+----------+---------------------------------+
    | copyright   | string   | Link to text or embedded text   |
    |             |          | containing a copyright notice   |
    +-------------+----------+---------------------------------+
    | license     | string   | Link to text or embedded text   |
    |             |          | containing license terms        |
    +-------------+----------+---------------------------------+
    | features    | array of | List of extension features used |
    |             | strings  |                                 |
    +-------------+----------+---------------------------------+
    | $comment    | string   | Source code comments only, no   |
    |             |          | semantics                       |
    +-------------+----------+---------------------------------+

            Table 1: Qualities of the Information Block

The version quality is used to indicate version information about the set of definitions in the SDF document πŸ”– The version is RECOMMENDED to be lexicographically increasing over the life of a model. A newer model always has a version string that string-compares higher than all previous versions πŸ“ˆ This is easily achieved by following the convention of starting the version with a date-time as defined in [RFC3339] or, if new versions are generated less frequently than once a day, just the full-date (i.e., YYYY-MM-DD). In many cases, that will be all that is needed (see Figure 1 for an example) πŸ¦‹ This specification doesn't give a strict definition for the format of the version string. Instead, each system or organization using the version string should define internal structure and semantics to the level needed for their use. If no further details are provided, a date-time or full-date in this field can be assumed to indicate the latest update time of the definitions in the SDF document ⏰

The modified quality can be used with a value using date-time as defined in [RFC3339] (with Z for time-zone) or full-date format to express the time of the latest revision of the definitions πŸ•°οΈ

The license string is preferably either a URI that points to a web page with an unambiguous definition of the license, or an [SPDX] license identifier. (As an example, for models to be handled by the One Data Model liaison group, this license identifier will typically be "BSD-3-Clause".) πŸ“œ

The features quality can be used to list names of critical (i.e., cannot be safely ignored) SDF extension features that need to be understood for the definitions to be properly processed 🚨 Extension feature names will be specified in extension documents. They can either be registered (see Section 7.5.4 for specifics, which make sure that a registered feature name does not contain a colon) or be a URI (which always contains a colon). Note that SDF processors are not expected to, and normally SHOULD NOT, dereference URIs used as feature names. Any representation retrievable under such a URI could be useful to humans, though πŸ‘€ (See [DEREF-ID-PATTERN] for a more extensive discussion of dereferenceable identifiers.)

3.2. Namespaces Block

The namespaces block contains the namespace map and the defaultNamespace setting. None of these qualities are required, and none have default values that are assumed if the quality is absent 🌸

The namespace map is a map from short names for URIs to the namespace URIs themselves πŸ—ΊοΈ

The defaultNamespace setting selects one of the entries in the namespace map by giving its short name. The associated URI (the value of this entry) becomes the default namespace for the SDF document πŸ‘‘

  +==================+========+===================================+
  | Quality          | Type   | Description                       |
  +==================+========+===================================+
  | namespace        | map    | Defines short names mapped to     |
  |                  |        | namespace URIs, to be used as     |
  |                  |        | identifier prefixes               |
  +------------------+--------+-----------------------------------+
  | defaultNamespace | string | Identifies one of the prefixes in |
  |                  |        | the namespace map to be used as a |
  |                  |        | default in resolving identifiers  |
  +------------------+--------+-----------------------------------+

                       Table 2: Namespaces Block

The following example declares a set of namespaces and defines cap as the default namespace πŸ’œ By convention, the values in the namespace map contain full URIs without a fragment identifier. The fragment identifier is then added, if needed, where the namespace entry is used 🧚

"namespace": {
  "cap": "https://example.com/capability/cap",
  "zcl": "https://zcl.example.com/sdf"
},
"defaultNamespace": "cap"

Multiple SDF documents can contribute to the same namespace by using the same namespace URI for the default namespace across the documents 🫢

If no defaultNamespace setting is given, the SDF document does not contribute to a global namespace. All definitions remain local to the model and are not accessible for re-use by other models πŸ™ˆ As the defaultNamespace is set by supplying a namespace short name, its presence requires a namespace map that contains a mapping for that namespace short name πŸ’…

If no namespace map is given, no short names for namespace URIs are set up, and no defaultNamespace can be given 🌈

3.3. Definitions Block ✨

okay bestie, the Definitions block is where the actual stuff lives πŸŽ€ It holds one or more groups, and each group is labelled with a Class Name Keyword like sdfObject or sdfProperty. There can only be one group per keyword at this level πŸ’… Putting all the individual definitions inside the group under that keyword is just a shortcut, so you don't have to repeat the class name keyword for every single definition. Iconic efficiency πŸ¦‹

Each group's value is a JSON map. The keys name the individual definitions in that group, and the values give a set of qualities (name-value pairs) for each one. (Lowkey, these map entries are also called "named sets of qualities" πŸ’œ)

Each group may hold zero or more definitions. Every identifier you define creates a new type and term in the target namespace 🌸 Declarations have a scope of the definition block they're directly contained in. Main character scoping πŸ‘‘

A definition can also contain other definitions 🧚 So each definition is a named set of qualities: the newly defined identifier plus a set of key-value pairs that represent the defined qualities and any contained definitions. It's giving nested dolls πŸͺ†

Here's an sdfObject definition in Figure 3 ✨

"sdfObject": {
  "foo": {
    "sdfProperty": {
      "bar": {
        "type": "boolean"
      }
    }
  }
}
                Figure 3: Example sdfObject Definition

This example defines an sdfObject "foo" in the default namespace (full address: #/sdfObject/foo). It contains a Property that can be addressed as #/sdfObject/foo/sdfProperty/bar, with data of type boolean πŸ’–

Often, definitions are also declarations 🫢 The definition of the entry "bar" in the Property "foo" means that data corresponding to the "foo" Property in a Property interaction offered by a Thing can have zero or one components modeled by "bar". Entries within sdfProperty, sdfAction, and sdfEvent that sit inside sdfObject or sdfThing entries are also declarations, but entries within sdfData are not πŸ™ˆ Similarly, sdfObject or sdfThing entries within an sdfThing definition specify that the interactions offered by a Thing modeled by this sdfThing include the interactions modeled by the nested sdfObject or sdfThing. Nesting is the whole vibe 🌈

3.4. Top-Level Affordances and sdfData ✨

Besides living inside an sdfObject or sdfThing, affordances (that's sdfProperty, sdfAction, and sdfEvent) and sdfData can also sit at the top level of an SDF document πŸ¦‹ Since they aren't attached to an sdfObject or sdfThing, these definitions are meant to be reused via the sdfRef mechanism (see Section 4.4) πŸ”₯

4. Names and Namespaces πŸ’œ

SDF documents may contribute to a global namespace and may reference elements from that global namespace. (An SDF document that doesn't set a defaultNamespace does not contribute to a global namespace πŸ’€)

4.1. Structure πŸŽ€

Global names look exactly like https:// URIs with attached fragment identifiers ✨

There's no intention to require that these URIs can be dereferenced. (However, since future extensions of SDF might find a use for dereferencing global names, the URI should be chosen so that this may become possible later. See also [DEREF-ID-PATTERN] for a discussion of dereferenceable identifiers 🧚)

The absolute-URI of a global name should be a URI as per Section 3 of RFC 3986 [STD66] with a scheme of "https" and a path (hier-part in [STD66]). For base SDF, the query part should not be used (it might be used in extensions 🌸)

The fragment identifier is constructed as per Section 6 of [RFC6901] πŸ’…

4.2. Contributing Global Names πŸ¦‹

The fragment identifier part of a global name defined in an SDF document is constructed from a JSON Pointer that selects the element defined for this name in the SDF document. The absolute-URI part is a copy of the default namespace 🫢

So the default namespace is always the target namespace for a name that gets a contributed definition. To really emphasize that name definitions go to the default namespace, this namespace is also called the "target namespace" of the SDF document πŸ‘‘

For instance, in Figure 1, definitions for the following global names are contributed πŸ’–

Note the #! It separates the absolute-URI part (Section 4.3 of RFC 3986 [STD66]) from the fragment identifier part (including the #, a JSON Pointer as in Section 6 of [RFC6901]) πŸ”₯

4.3. Referencing Global Names 🌈

A name reference takes the form of the production curie in Section 3 of [W3C.NOTE-curie-20101216], but limits the IRIs involved in that grammar to URIs as per [STD66] and the prefixes to ASCII characters [STD80]. (Note that this definition does not make use of the production safe-curie in [W3C.NOTE-curie-20101216] πŸ™ˆ)

A name that is contributed by the current SDF document can be referenced by a Same-Document Reference as per Section 4.4 of RFC 3986 [STD66]. As there's little point in referencing the entire SDF document, this will be a # followed by a JSON Pointer ✨ This is the only kind of name reference to itself that's possible in an SDF document that does not set a default namespace πŸ’…

Name references that point outside the current SDF document need to contain CURIE prefixes. These then reference namespace declarations in the namespaces block πŸ¦‹

For example, if a namespace prefix is defined:

"namespace": {
  "foo": "https://example.com/"
}

then this reference to that namespace:

"sdfRef": "foo:#/sdfData/temperatureData"

references the global name:

"https://example.com/#/sdfData/temperatureData"

Note that there's no way to provide a URI scheme name in a CURIE, so all references to outside the document need to go through the namespace map πŸŽ€

Name references occur only in specific elements of the SDF syntax:

  • copying elements via sdfRef values
  • pointing to elements via sdfRequired value elements

4.4. sdfRef πŸ’–

In a JSON map establishing a definition, the keyword sdfRef is used to copy the qualities and enclosed definitions of the referenced definition, indicated by the included name reference, into the newly formed definition. (This can be compared to the processing of the $ref keyword in [JSO7] 🧚) The referenced definition should be such that, after copying and applying the additional qualities in the referencing definition, the newly built definition is also valid SDF (e.g., the copied qualities and definitions are valid in the context of the new definition 🫢)

For example, this reference:

"temperatureProperty": {
  "sdfRef": "#/sdfData/temperatureData"
}

creates a new definition "temperatureProperty" that contains all of the qualities defined in the definition at /sdfData/temperatureData ✨

The sdfRef member need not be the only member of a map. Additional members may be present with the intention of overriding parts of the referenced map or adding new qualities or definitions 🌸

When processing sdfRef, if the target definition also contains sdfRef (i.e., is based on yet another definition), that MUST be processed as well πŸ’…

More formally, for a JSON map that contains an sdfRef member, the semantics are defined to be as if the following steps were performed πŸͺ†

  1. The JSON map that contains the sdfRef member is copied into a variable named "patch".
  2. The sdfRef member of the copy in "patch" is removed.
  3. The JSON Pointer that is the value of the sdfRef member is dereferenced and the result is copied into a variable named "original".
  4. The JSON Merge Patch algorithm [RFC7396] is applied to patch the contents of "original" with the contents of "patch".
  5. The result of the Merge Patch is used in place of the value of the original JSON map.

Note that the formal syntaxes given in Appendices A and B generally describe the _result_ of applying a merge-patch. The notations aren't powerful enough to describe, for instance, how the merge-patch algorithm causes null values within the sdfRef to remove members of JSON maps from the referenced target 😭 Still, the syntaxes also give the syntax of the sdfRef itself, which vanishes during resolution. So in many cases, even merge-patch inputs will validate with these formal syntaxes πŸ’œ

Given the example (Figure 1) and the following definition:

{
  "info": {
    "title": "Example light switch using sdfRef"
  },
  "namespace": {
    "cap": "https://example.com/capability/cap"
  },
  "defaultNamespace": "cap",
  "sdfObject": {
    "BasicSwitch": {
      "sdfRef": "cap:#/sdfObject/Switch",
      "sdfAction": {
        "toggle": null
      }
    }
  }
}

The resulting definition of the "BasicSwitch" sdfObject would be identical to the definition of the "Switch" sdfObject, except it would not contain the "toggle" Action πŸ™ˆ

{
  "info": {
    "title": "Example light switch using sdfRef"
  },
  "namespace": {
    "cap": "https://example.com/capability/cap"
  },
  "defaultNamespace": "cap",
  "sdfObject": {
    "BasicSwitch": {
      "sdfProperty": {
        "value": {
          "description":
"The state of the switch; false for off and true for on.",
          "type": "boolean"
        }
      },
      "sdfAction": {
        "on": {
          "description":
"Turn the switch on; equivalent to setting value to true."
        },
        "off": {
          "description":
"Turn the switch off; equivalent to setting value to false."
        }
      }
    }
  }
}

4.4.1. Resolved Models πŸ¦‹

A model where all sdfRef references are processed as described in Section 4.4 is called a resolved model ✨

For example, given the following sdfData definitions:

"sdfData": {
  "Coordinate" : {
    "type": "number", "unit": "m"
  },
  "X-Coordinate" : {
    "sdfRef" : "#/sdfData/Coordinate",
    "description":
"Distance from the base of the Thing along the X axis."
  },
  "Non-neg-X-Coordinate" : {
    "sdfRef": "#/sdfData/X-Coordinate",
    "minimum": 0
  }
}

the definitions would look as follows after being resolved πŸ’…

"sdfData": {
  "Coordinate" : {
    "type": "number", "unit": "m"
  },
  "X-Coordinate" : {
    "description":
"Distance from the base of the Thing along the X axis.",
    "type": "number", "unit": "m"
  },
  "Non-neg-X-Coordinate" : {
    "description":
"Distance from the base of the Thing along the X axis.",
    "minimum": 0, "type": "number", "unit": "m"
  }
}

4.5. sdfRequired πŸ’–

okay bestie, sdfRequired is the keyword that says which declarations have to be represented in a grouping (sdfThing or sdfObject) defined by the current definition. it's giving "you must bring these" energy πŸ’…

the value is an array of references, and each one points at one or more declarations that are mandatory to be represented. 🌸

the references in that array can be SDF names (JSON Pointers) or one of two shortened formats:

  • a text string holding a "referenceable-name", which means an affordance name or a grouping name:
    • every affordance declaration that sits directly in the same grouping (so not nested further inside another grouping) and carries that name is declared mandatory to be represented. there can be more than one of these, one per affordance type ✨
    • the same goes for groupings that are made mandatory inside the groupings that contain them πŸ¦‹
  • the Boolean value true. the affordance or grouping that carries the sdfRequired keyword is declared mandatory to be represented πŸŽ€

ngl, referenceable-names don't get the encoding that JSON Pointers need, which is covered in Section 2.3.2. so they can't contain ":" or "#" characters, which keeps them from getting mixed up with JSON Pointers (see the referenceable-name rule in Appendix A). πŸ™ˆ

if a Given Name does have ":" or "#" in it, you'll need to write a JSON Pointer to reference it in "sdfRequired" instead. that can mean extra path elements plus JSON Pointer encoding, which is a lot of extra work. the easy fix is to just pick Given Names without those characters πŸ’€

Figure 4 is giving a whole vibe 😭. it shows two required elements in the sdfObject definition "temperatureWithAlarm": the sdfProperty "currentTemperature" and the sdfEvent "overTemperatureEvent". it also shows JSON Pointers used with "sdfRef" to reuse an existing definition, both for the sdfProperty "currentTemperature" and for the sdfOutputData that the sdfEvent "overTemperatureEvent" produces. ✨

"sdfObject": {
  "temperatureWithAlarm": {
    "sdfRequired": [
"#/sdfObject/temperatureWithAlarm/sdfProperty/currentTemperature",
"#/sdfObject/temperatureWithAlarm/sdfEvent/overTemperatureEvent"
    ],
    "sdfData":{
       "temperatureData": {
        "type": "number"
      }
    },
    "sdfProperty": {
      "currentTemperature": {
"sdfRef": "#/sdfObject/temperatureWithAlarm/sdfData/temperatureData",
      "writable": false
      }
    },
    "sdfEvent": {
      "overTemperatureEvent": {
       "sdfOutputData": {
"sdfRef": "#/sdfObject/temperatureWithAlarm/sdfData/temperatureData"
        }
      }
    }
  }
}

Figure 4: Using sdfRequired πŸŽ€

in Figure 4, that same sdfRequired can also be written in short form:

"sdfRequired": ["currentTemperature", "overTemperatureEvent"]

or, for instance, "overTemperatureEvent" could carry this instead:

"overTemperatureEvent": {
  "sdfRequired": [true],
  "...": "..."
}

4.6. Common Qualities πŸ“

all the definitions in SDF share a bunch of qualities that give them metadata. they're listed in Table 3 ✨. none of these qualities are required, and none of them have default values that get assumed when they're missing. 🌈

if an application needs a short text description and the SDF model doesn't give a label, the app can use the last part of the JSON Pointer to the definition in its place. that's the last reference-token, from Section 3 of [RFC6901]. so smart, honestly 🧚

+=============+==============+=============================+
| Quality     | Type         | Description                 |
+=============+==============+=============================+
| description | string       | long text (no constraints)  |
+-------------+--------------+-----------------------------+
| label       | string       | short text (no constraints) |
+-------------+--------------+-----------------------------+
| $comment    | string       | source code comments only,  |
|             |              | no semantics                |
+-------------+--------------+-----------------------------+
| sdfRef      | sdf-pointer  | (see Section 4.4)           |
+-------------+--------------+-----------------------------+
| sdfRequired | pointer-list | (see Section 4.5, used in   |
|             |              | affordances or groupings)   |
+-------------+--------------+-----------------------------+

Table 3: Common Qualities πŸ¦‹

4.7. Data Qualities πŸ”₯

data qualities show up in sdfData and sdfProperty definitions. they're named sets of data qualities, which the spec abbreviates as named-sdq πŸ’œ.

these qualities cover the common qualities, the JSO-inspired qualities (more on those below), and the data qualities that this specification defines itself. the last group is listed in Table 4 πŸ‘‘.

Appendix C lists the data qualities inspired by the different proposals at json-schema.org. the goal is that these information model-level qualities stay compatible with the data model semantics from the versions of the json-schema.org proposal they were imported from. no drama, just compatibility 🫢

+===============+================+====================+=========+
| Quality       | Type           | Description        | Default |
+===============+================+====================+=========+
| (common)      |                | Section 4.6        |         |
+---------------+----------------+--------------------+---------+
| unit          | string         | unit name (note 1) | N/A     |
+---------------+----------------+--------------------+---------+
| nullable      | boolean        | indicates a null   | true    |
|               |                | value is available |         |
|               |                | for this type      |         |
+---------------+----------------+--------------------+---------+
| contentFormat | string         | content type (IANA | N/A     |
|               |                | media type string  |         |
|               |                | plus parameters),  |         |
|               |                | encoding (note 2)  |         |
+---------------+----------------+--------------------+---------+
| sdfType       | string         | sdfType            | N/A     |
|               | (Section       | enumeration        |         |
|               | 4.7.1)         | (extensible)       |         |
+---------------+----------------+--------------------+---------+
| sdfChoice     | named set of   | named alternatives | N/A     |
|               | data qualities |                    |         |
|               | (Section       |                    |         |
|               | 4.7.2)         |                    |         |
+---------------+----------------+--------------------+---------+
| enum          | array of       | abbreviation for   | N/A     |
|               | strings        | string-valued      |         |
|               |                | named alternatives |         |
+---------------+----------------+--------------------+---------+

Table 4: SDF-Defined Qualities of sdfData and sdfProperty

  1. the unit name SHOULD be taken from the "SenML Units" registry or the "Secondary Units" registry in [IANA.senml]. that's per Sections 4.5.2 and 12.1 of [RFC8428] and Section 3 of [RFC8798], respectively. ✨

sometimes a registration can't be obtained, or it just wouldn't fit. in that case the unit name can be a URI that points to a definition of the unit, which is the exception here πŸ¦‹. heads up though: SDF processors are not expected to dereference these URIs, and they normally SHOULD NOT do it. the definition at the URI may still be helpful for humans 🧚. (see [DEREF-ID-PATTERN] for a deeper dive on dereferenceable identifiers.)

a URI unit name is told apart from a registered unit name by the colon. so any registered unit names with a colon in them (none at the time of writing!) can't be used directly in SDF πŸ™ˆ.

translators into ecosystems that need URIs for unit names get the URN sub-namespace "urn:ietf:params:unit" (see Section 7.3). URNs from this sub-namespace MUST NOT be used in a unit quality when you could just write the unit name, like kg instead of urn:ietf:params:unit:kg. the only exception is when the unit name has a colon and therefore can't be used directly in SDF πŸ’….

  1. the contentFormat quality follows the Content-Format-Spec from Section 6 of [RFC9193]. it lets you express both numeric and string-based Content-Formats 🌈.

4.7.1. sdfType πŸŽ€

SDF defines a bunch of basic types on top of the ones JSON or JSO already give you. these types are identified by the sdfType quality, which is a text string from a set of type names. those names live in the "sdfType values" registry inside the "Semantic Definition Format (SDF)" registry group (Section 7.5.3) 🌸. an sdfType name is made of lowercase ASCII letters, digits, and - (ASCII hyphen/minus) characters. it starts with a lowercase ASCII letter, so the pattern is "[a-z][-a-z0-9]*". when a name is built from several words, it usually uses kebab-case [KebabCase] πŸ“.

to help with interworking with JSO implementations, it is RECOMMENDED that sdfType is always used alongside the type quality inherited from [JSO7V]. doing that gives you a common representation of the type's values in JSON. ✨

the sdfType values this specification defines are in Table 5. the table also describes what each sdfType means, the conventional value for type to use with it, and a conventional JSON representation for its values. the type and the JSON representation are picked to match each other, and this MUST be true for any sdfType values registered later too πŸ‘‘.

+=============+=============+========+================+============+
| Name        | Description | type   | JSON           | Reference  |
|             |             |        | Representation |            |
+=============+=============+========+================+============+
| byte-string | A sequence  | string | base64url      | Section    |
|             | of zero or  |        | without        | 3.4.5.2 of |
|             | more bytes  |        | padding        | RFC 8949   |
|             |             |        |                | [STD94]    |
+-------------+-------------+--------+----------------+------------+
| unix-time   | A point in  | number | POSIX time     | Section    |
|             | civil time  |        |                | 3.4.2 of   |
|             | (note 1)    |        |                | RFC 8949   |
|             |             |        |                | [STD94]    |
+-------------+-------------+--------+----------------+------------+

Table 5: Values Defined in Base SDF for the sdfType Quality

(1) quick note on unix-time πŸ™ˆ: its definition doesn't mean you can represent points in time that land on leap seconds. more date/time-related sdfTypes will probably get added to the sdfType value registry later πŸ¦‹.

4.7.2. sdfChoice

Okay bestie, this one is giving buffet πŸ“βœ¨ Data can be a choice of named alternatives called sdfChoice. Each alternative gets a name (a string, used as the key in the outer JSON map that represents the whole choice) and a set of dataqualities (each in its own inner JSON map, which is the value used for that alternative in the outer map) πŸŽ€. Any dataqualities set at the same level as the sdfChoice apply to every choice, except for the choices where that dataquality gets overridden at the choice level πŸ’….

sdfChoice merges the functions of two constructs from [JSO7V] πŸ¦‹

enum πŸ’–

What could be expressed as:

"enum": ["foo", "bar", "baz"]

in JSO, is often best represented as:

"sdfChoice": {
  "foo": { "description": "This is a foonly"},
  "bar": { "description":
           "As defined in the second world congress"},
  "baz": { "description": "From bigzee foobaz"}
}

This lets you place other dataqualities, like description in the example, right there 🌸

If an enum needs a data type that's different from the text string, what would have been:

"type": "number",
"enum": [1, 2, 3]

in JSO, is represented as:

"type": "number",
"sdfChoice": {
  "a-better-name-for-alternative-1": { "const": 1 },
  "alternative-2": { "const": 2 },
  "the-third-alternative": { "const": 3 }
}

The string names should obviously be picked so they describe what those numbers actually stand for 🧚 sdfChoice also makes it easy to add number ranges into the mix ✨

(Note that const can also be used for strings, like in the previous example, for instance if the actual string value is indeed a crucial element for the data model πŸ’œ)

anyOf 🌈

JSO has a type union called anyOf, which gives you a choice between anonymous alternatives.

What could have been in JSO:

"anyOf": [
  {"type": "array", "minItems": 3, "maxItems": "3",
   "items": {"$ref": "#/sdfData/rgbVal"}},
  {"type": "array", "minItems": 4, "maxItems": "4",
   "items": {"$ref": "#/sdfData/cmykVal"}}
]

can be notated more descriptively in SDF as:

"sdfChoice": {
  "rgb": {"type": "array", "minItems": 3, "maxItems": "3",
          "items": {"sdfRef": "#/sdfData/rgbVal"}},
  "cmyk": {"type": "array", "minItems": 4, "maxItems": "4",
           "items": {"sdfRef": "#/sdfData/cmykVal"}}
}
⚠️ Not needed in SDF: the type intersection construct allOf, or the peculiar type-xor construct oneOf from [JSO7V]. SDF doesn't need either one πŸ™ˆ

As a simplification for SDF users who are used to the JSO enum keyword, enum is kept, but only for a choice of text string values. So:

"enum": ["foo", "bar", "baz"]

is syntactic sugar for:

"sdfChoice": {
  "foo": { "const": "foo"},
  "bar": { "const": "bar"},
  "baz": { "const": "baz"}
}

In a single definition, enum and sdfChoice can't both be used at the same time, because enum is just syntactic sugar for sdfChoice πŸ™…β€β™€οΈ


5. Keywords for Definition Groups ✨

The following SDF keywords are used to create definition groups in the target namespace πŸŽ€ All of these definitions share some common qualities, as discussed in Section 4.6 πŸ’…

5.1. sdfObject

The sdfObject keyword denotes a group of zero or more sdfObject definitions 🧸 An sdfObject definition may contain or include definitions of named Properties, Actions, and Events declared for the sdfObject, plus named data types (the sdfData group) to be used in this or other sdfObjects πŸ’–

The qualities of an sdfObject include the common qualities. Additional qualities are shown in Table 6. None of these qualities are required, and none of them have default values that get assumed if the quality is absent 🌸

    +=============+===========+================================+
    | Quality     | Type      | Description                    |
    +=============+===========+================================+
    | (common)    |           | Section 4.6                    |
    +-------------+-----------+--------------------------------+
    | sdfProperty | property  | zero or more named property    |
    |             |           | definitions for this sdfObject |
    +-------------+-----------+--------------------------------+
    | sdfAction   | action    | zero or more named action      |
    |             |           | definitions for this sdfObject |
    +-------------+-----------+--------------------------------+
    | sdfEvent    | event     | zero or more named event       |
    |             |           | definitions for this sdfObject |
    +-------------+-----------+--------------------------------+
    | sdfData     | named-sdq | zero or more named data type   |
    |             |           | definitions that might be used |
    |             |           | in the above                   |
    +-------------+-----------+--------------------------------+
    | minItems    | number    | (array) minimum number of      |
    |             |           | multiplied affordances in      |
    |             |           | array                          |
    +-------------+-----------+--------------------------------+
    | maxItems    | number    | (array) maximum number of      |
    |             |           | multiplied affordances in      |
    |             |           | array                          |
    +-------------+-----------+--------------------------------+

                 Table 6: Qualities of sdfObject

5.2. sdfProperty

The sdfProperty keyword denotes a group of zero or more Property definitions πŸ”₯

Properties are used to model elements of state 🧠

The qualities of a Property definition include the data qualities (and therefore the common qualities). See Section 4.7. Additional qualities are shown in Table 7 πŸ¦‹

 +============+=========+===============================+=========+
 | Quality    | Type    | Description                   | Default |
 +============+=========+===============================+=========+
 | (data)     |         | Section 4.7                   |         |
 +------------+---------+-------------------------------+---------+
 | readable   | boolean | Reads are allowed             | true    |
 +------------+---------+-------------------------------+---------+
 | writable   | boolean | Writes are allowed            | true    |
 +------------+---------+-------------------------------+---------+
 | observable | boolean | Flag to indicate asynchronous | true    |
 |            |         | notification is available     |         |
 +------------+---------+-------------------------------+---------+

                Table 7: Qualities of sdfProperty

5.3. sdfAction

The sdfAction keyword denotes a group of zero or more Action definitions 🎬

Actions are used to model commands and methods that get invoked ⚑ Actions may have parameter data that's supplied upon invocation, and output data that's provided as a direct result of invoking the action. (Note that "action objects" may also be created to furnish ongoing information during a long-running action. These would be pointed to by the output data πŸ‘€)

The qualities of an Action definition include the common qualities. Additional qualities are shown in Table 8. None of these qualities are required, and none of them have default values that get assumed if the quality is absent πŸ’…

    +===============+===========+============================+
    | Quality       | Type      | Description                |
    +===============+===========+============================+
    | (common)      |           | Section 4.6                |
    +---------------+-----------+----------------------------+
    | sdfInputData  | map       | data qualities of the      |
    |               |           | input data for an Action   |
    +---------------+-----------+----------------------------+
    | sdfOutputData | map       | data qualities of the      |
    |               |           | output data for an Action  |
    +---------------+-----------+----------------------------+
    | sdfData       | named-sdq | zero or more named data    |
    |               |           | type definitions that      |
    |               |           | might be used in the above |
    +---------------+-----------+----------------------------+

                 Table 8: Qualities of sdfAction

sdfInputData defines the input data of the action. sdfOutputData defines the output data of the action. As discussed in Section 2.2.3, a set of data qualities with type "object" can be used to substructure either data item, and optionality is indicated by the data quality required 🧩

5.4. sdfEvent

The sdfEvent keyword denotes zero or more Event definitions πŸ“£

Events are used to model asynchronous occurrences that may be communicated proactively. Events have data elements that are communicated when the event occurs πŸ””

The qualities of sdfEvent include the common qualities. Additional qualities are shown in Table 9. None of these qualities are required, and none of them have default values that get assumed if the quality is absent 🌈

    +===============+===========+============================+
    | Quality       | Type      | Description                |
    +===============+===========+============================+
    | (common)      |           | Section 4.6                |
    +---------------+-----------+----------------------------+
    | sdfOutputData | map       | data qualities of the      |
    |               |           | output data for an Event   |
    +---------------+-----------+----------------------------+
    | sdfData       | named-sdq | zero or more named data    |
    |               |           | type definitions that      |
    |               |           | might be used in the above |
    +---------------+-----------+----------------------------+

                  Table 9: Qualities of sdfEvent

sdfOutputData defines the output data of the action. As discussed in Section 2.2.4, a set of data qualities with type "object" can be used to substructure the output data item, and optionality is indicated by the data quality required πŸ’œ

5.5. sdfData

The sdfData keyword denotes a group of zero or more named data type definitions (named-sdq) πŸ—‚οΈ

An sdfData definition provides a reusable semantic identifier for a type of data item, and it describes the constraints on that defined type ✨ sdfData is not itself a declaration, so it doesn't cause any of these data items to be included in an affordance definition πŸ™Œ

The qualities of sdfData include the data qualities (and therefore the common qualities). See Section 4.7.

6. High-Level Composition 🧩✨

okay so this section is all about snapping big models together from smaller pieces, and bestie, the vibe is "build a whole product from building blocks 🧱 without ever breaking the atomicity of each sdfObject" πŸ’– here's what it has to let you do:

  • Represent products, standardized product types, and modular products, all while keeping every sdfObject atomic πŸŽ€
  • Bundle a reusable definition block out of sdfObjects. For example, one plug on an outlet strip could be made of sdfObjects for on/off control, an energy monitor, and an optional dimmer, and each of those sdfObjects stays atomic on its own πŸ”Œ
  • Combine sdfObjects and other definition blocks into a bigger sdfThing that represents a whole product, still keeping every sdfObject atomic πŸ‘‘
  • Enrich and refine a base definition with product-specific qualities and quality values, like unit, range, and scale settings πŸ“
  • Reference things in one part of a complex definition from another part of that same definition. For example, summarizing the energy readings from every plug on an outlet strip πŸ”‹

6.1. Paths in the Model Namespaces

okay so the model namespace is organized by terms defined in the SDF documents that feed into it πŸ“š and if an organization or vendor is defining stuff, their definitions are expected to live in a namespace that's specific to them 🏷️ very main character behavior.

The structure of a path in a namespace comes from the JSON Pointers that point to definitions in the SDF documents in that namespace πŸ—ΊοΈ for example, say there's an SDF document defining an sdfObject "Switch" with an action "on". Then the reference to that action would be ns:#/sdfObject/Switch/sdfAction/on, where ns is the namespace prefix (the short name for the namespace) ✨

6.2. Modular Composition

Modular composition is the move where an existing definition (it could be in the same SDF document or a totally different one πŸ€·β€β™€οΈ) becomes part of a new definition, just by including a reference to it inside the model namespace 🧩 it's giving LEGO, honestly πŸ§±πŸ’œ

6.2.1. Use of the "sdfRef" Keyword to Reuse a Definition

So an existing definition can work as a template for a brand new one πŸŽ€ basically, you create a new definition in the target namespace that borrows the defined qualities of some existing definition. To do this, you use the keyword sdfRef as a quality of the new definition, with a value that's a reference to the template definition 🫢

In the definition that uses sdfRef, you can add new qualities and override existing qualities from the referenced definition πŸ’… (heads up: JSON maps don't have a defined order, so the SDF processor might see these overrides before it sees the sdfRef πŸ™ˆ)

Also, the referenced definition might have qualities or definitions that aren't valid where the sdfRef is being used. If that happens, the resulting model might be invalid once it's resolved πŸ’€ for example, an sdfRef that adds an sdfThing definition inside an sdfObject definition. Yikes 😭

Now, the convention here is that overrides are meant to be used only for further restricting the allowable set of data values πŸ”’ Figure 5 shows this: any value that's allowed for a cable-length is also allowed for a length, but with the extra rule that the length can't be smaller than 5 cm. (This is labeled a convention because it can't be checked in the general case πŸ™ƒ a quality of implementation thing for a tool might be to offer at least some kind of checking πŸ› οΈ) Also, the example overrides the description of the referenced definition. Since that quality is for humans to read, there's no conflict with what we're going for πŸ’–

"sdfData":
  "length" : {
    "type": "number",
    "minimum": 0,
    "unit": "m"
    "description": "There can be no negative lengths."
  }
...
  "cable-length" : {
    "sdfRef": "#/sdfData/length"
    "minimum": 5e-2,
    "description": "Cables must be at least 5 cm."
  }

Figure 5: Using an Override to Further Restrict the Set of Data Values

6.3. sdfThing

okay the sdfThing is a set of declarations and qualities that can be part of a bigger, more complex model 🧚 like, the sdfObject declarations that make up one socket of an outlet strip could get wrapped up in an sdfThing, and that sdfThing could then be used in the sdfThing definition for the whole outlet strip. (Check out Figure 7 in Appendix D.1 for parts of an SDF model for this example πŸ“–)

sdfThing definitions carry semantic meaning. Think of a defined refrigerator compartment and a defined freezer compartment, which together make up a refrigerator-freezer product 🧊 An sdfThing can be built out of sdfObjects and other sdfThings. It can also hold sdfData definitions, plus its own interaction affordance declarations, like a status (on/off) for the whole refrigerator-freezer πŸ“ (see Figure 8 in Appendix D.2 for an example SDF model that shows all of this 🌈)

Table 10 lists the qualities of sdfThing πŸ’… none of these qualities are required, and none have default values that get assumed when the quality is missing. Just like sdfObject, having one or both of the optional qualities "minItems" and "maxItems" is what makes the sdfThing an array πŸŽ€

+=============+===========+=============================+
| Quality     | Type      | Description                 |
+=============+===========+=============================+
| (common)    |           | Section 4.6                 |
+-------------+-----------+-----------------------------+
| sdfThing    | thing     |                             |
+-------------+-----------+-----------------------------+
| sdfObject   | object    |                             |
+-------------+-----------+-----------------------------+
| sdfProperty | property  | zero or more named property |
|             |           | definitions for this thing  |
+-------------+-----------+-----------------------------+
| sdfAction   | action    | zero or more named action   |
|             |           | definitions for this thing  |
+-------------+-----------+-----------------------------+
| sdfEvent    | event     | zero or more named event    |
|             |           | definitions for this thing  |
+-------------+-----------+-----------------------------+
| sdfData     | named-sdq | zero or more named data     |
|             |           | type definitions that might |
|             |           | be used in the above        |
+-------------+-----------+-----------------------------+
| minItems    | number    | (array) minimum number of   |
|             |           | multiplied affordances in   |
|             |           | array                       |
+-------------+-----------+-----------------------------+
| maxItems    | number    | (array) maximum number of   |
|             |           | multiplied affordances in   |
|             |           | array                       |
+-------------+-----------+-----------------------------+

Table 10: Qualities of sdfThing

7. IANA Considerations 🌷

7.1. Media Type

IANA added a new Media-Type to the "Media Types" registry [IANA.media-types] πŸ¦‹ here's the entry:

+==========+======================+=======================+
| Name     | Template             | Reference             |
+==========+======================+=======================+
| sdf+json | application/sdf+json | RFC 9880, Section 7.1 |
+----------+----------------------+-----------------------+

Table 11: Media Type Registration for SDF

And here's the full registration template πŸ“

Type name:  application

Subtype name:  sdf+json

Required parameters:  N/A

Optional parameters:  N/A

Encoding considerations:  binary (JSON is UTF-8-encoded text)

Security considerations:  Section 8 of RFC 9880

Interoperability considerations:  none

Published specification:  Section 7.1 of RFC 9880

Applications that use this media type:  Tools for data and
   interaction modeling in the Internet of Things and related
   environments.

Fragment identifier considerations:  A JSON Pointer fragment
   identifier may be used as defined in Section 6 of [RFC6901].

Additional information:

   Magic number(s):  n/a
   File extension(s):  .sdf.json
   Windows Clipboard Name:  "Semantic Definition Format (SDF) for
      Data and Interactions of Things"
   Macintosh file type code(s):  n/a
   Macintosh Universal Type Identifier code:  org.ietf.sdf-json
      conforms to public.text

Person & email address to contact for further information:  ASDF WG
   mailing list (asdf@ietf.org) or IETF Applications and Real-Time
   Area (art@ietf.org)

Intended usage:  COMMON

Restrictions on usage:  none

Author/Change controller:  IETF

Provisional registration:  no

7.2. Content-Format

IANA added a new Content-Format to the "CoAP Content-Formats" registry, which sits inside the "Constrained RESTful Environments (CoRE) Parameters" registry group [IANA.core-parameters] ✨ here it is:

+======================+================+=====+===========+
| Content Type         | Content Coding | ID  | Reference |
+======================+================+=====+===========+
| application/sdf+json | -              | 434 | RFC 9880  |
+----------------------+----------------+-----+-----------+

Table 12: SDF Content-Format Registration

7.3. IETF URN Sub-Namespace for Unit Names (urn:ietf:params:unit)

IANA registered the following value in the "IETF URN Sub-namespace for Registered Protocol Parameter Identifiers" registry in [IANA.params], following the template in [BCP73] πŸ”–

Registry name: unit

Specification: RFC 9880

Repository: this one's a combo 🍹 it's made by putting together the symbol values from the "SenML Units" registry and the "Secondary Units" registry in the "Sensor Measurement Lists (SenML)" registry group [IANA.senml], as specified by Sections 4.5.2 and 12.1 of [RFC8428] and Section 3 of [RFC8798], respectively. By the registration policy, those two are guaranteed to not overlap 🫢

Index value: any characters in unit names need percent-encoding (Section 2.1 of RFC 3986 [STD66]) unless they're in the set "unreserved" (Section 2.3 of RFC 3986 [STD66]), the set "sub-delims" (Section 2.2 of RFC 3986 [STD66]), or they're ":" or "@". Basically, the result has to match the ABNF rule "pchar" in Section 3.3 of RFC 3986 [STD66] 🧁

7.4. SenML Registry Group

IANA added the following note to the "Sensor Measurement Lists (SenML)" registry group [IANA.senml] πŸ“£

In SDF [RFC9880], a URI unit name is distinguished from a registered unit name by the presence of a colon; any registered unit name that contains a colon can therefore not be directly used in SDF.

7.5. Registries

IANA made the "Semantic Definition Format (SDF)" registry group, and it holds all the registries defined in this section πŸ—‚οΈπŸ’œ

7.5.1. SDF Quality Names ✨

okay so IANA made a registry called "SDF Quality Names" πŸ“‹ It lives in the "Semantic Definition Format (SDF)" registry group, and every entry follows this template:

Name: a Quality Name made of ASCII letters, digits, and dollar signs πŸ’². It has to start with a lowercase ASCII letter or a dollar sign, so the pattern is [a-z$][A-Za-z$0-9]*.

Brief Description: a brief description πŸ“

Reference: a pointer to a specification πŸ”—

Change Controller: (See Section 2.3 of RFC 8126 [BCP26]) πŸŽ€

Quality Names here are meant to get registered alongside RFCs and IETF activities πŸ¦‹ The registration policy is Specification Required, as per Section 4.6 of RFC 8126 [BCP26]. It's not "RFC Required" or "IETF Review" (Sections 4.7 and 4.8 of RFC 8126 [BCP26]) πŸ’… The reason is so registrations can happen earlier in the process, even earlier than foreseen in [BCP100].)

The Experts have two jobs, bestie 🫢

  • Make sure the specification is available in an immutable reference and has gotten a good level of review, either alongside RFCs or IETF activities.
  • Be frugal when handing out Quality Names that sound like generally applicable semantics 🌸 Save them for qualities that are likely to get lots of use and can really benefit from being short and sweet ✨

The "SDF Quality Names" registry starts out with the entries in Table 13 πŸ“ All of their references point to RFC 9880 (this document), and all change controllers are "IETF" πŸ‘‘

    +======================+==========================================+
    | Name                 | Brief Description                        |
    +======================+==========================================+
    | $comment             | source code comments only, no semantics  |
    +----------------------+------------------------------------------+
    | const                | constant value                           |
    +----------------------+------------------------------------------+
    | contentFormat        | content format                           |
    +----------------------+------------------------------------------+
    | default              | default value                            |
    +----------------------+------------------------------------------+
    | description          | long description text                    |
    +----------------------+------------------------------------------+
    | enum                 | sdfChoice limited to text strings        |
    +----------------------+------------------------------------------+
    | exclusiveMaximum     | exclusive maximum for a number           |
    +----------------------+------------------------------------------+
    | exclusiveMinimum     | exclusive minimum for a number           |
    +----------------------+------------------------------------------+
    | format               | specific format for a text string        |
    +----------------------+------------------------------------------+
    | items                | items of an array                        |
    +----------------------+------------------------------------------+
    | label                | short text (no constraints); defaults to |
    |                      | key                                      |
    +----------------------+------------------------------------------+
    | maxItems             | maximum number of items in an array      |
    +----------------------+------------------------------------------+
    | maxLength            | maximum length for a text string (in     |
    |                      | characters, i.e., Unicode scalar values) |
    +----------------------+------------------------------------------+
    | maximum              | maximum for a number                     |
    +----------------------+------------------------------------------+
    | minItems             | minimum number of items in an array      |
    +----------------------+------------------------------------------+
    | minLength            | minimum length for a text string (in     |
    |                      | characters, i.e., Unicode scalar values) |
    +----------------------+------------------------------------------+
    | minimum              | minimum for a number                     |
    +----------------------+------------------------------------------+
    | multipleOf           | step size of number                      |
    +----------------------+------------------------------------------+
    | nullable             | boolean: can the item be left out?       |
    +----------------------+------------------------------------------+
    | observable           | boolean: can the item be observed?       |
    +----------------------+------------------------------------------+
    | pattern              | regular expression pattern for a text    |
    |                      | string                                   |
    +----------------------+------------------------------------------+
    | properties           | named dataqualities for type="object"    |
    +----------------------+------------------------------------------+
    | readable             | boolean: can the item be read?           |
    +----------------------+------------------------------------------+
    | required             | which data items are required?           |
    +----------------------+------------------------------------------+
    | sdfChoice            | named dataqualities for a choice         |
    +----------------------+------------------------------------------+
    | sdfData              | named dataqualities for an independent   |
    |                      | data type definition                     |
    +----------------------+------------------------------------------+
    | sdfInputData         | input data to an action                  |
    +----------------------+------------------------------------------+
    | sdfOutputData        | output data of an action or event        |
    |                      | (sdfRequired applies here)               |
    +----------------------+------------------------------------------+
    | sdfRef               | sdf-pointer to definition being          |
    |                      | referenced                               |
    +----------------------+------------------------------------------+
    | sdfRequired          | pointer-list to declarations of required |
    |                      | components                               |
    +----------------------+------------------------------------------+
    | sdfRequiredInputData | pointer-list to declarations of required |
    |                      | input data for an action                 |
    +----------------------+------------------------------------------+
    | sdfType              | more detailed information about the type |
    |                      | of a string                              |
    +----------------------+------------------------------------------+
    | type                 | general category of data type            |
    +----------------------+------------------------------------------+
    | uniqueItems          | boolean: do the items of the array need  |
    |                      | to be all different?                     |
    +----------------------+------------------------------------------+
    | unit                 | engineering unit and scale (per SenML    |
    |                      | registry)                                |
    +----------------------+------------------------------------------+
    | writable             | boolean: can the item be written to?     |
    +----------------------+------------------------------------------+

        Table 13: Initial Content of the SDF Quality Names Registry

7.5.2. SDF Quality Name Prefixes 🧚

Next up is the "SDF Quality Name Prefixes" registry, also in the "Semantic Definition Format (SDF)" registry group πŸ’œ Its template goes like this:

Prefix: a Quality Name prefix made of lowercase ASCII letters and digits, starting with a lowercase ASCII letter, so the pattern is [a-z][a-z0-9]*.

Contact: a contact point for the organization that hands out Quality Names with this prefix πŸ“ž

Reference: a pointer to extra information, if there is any πŸ”Ž

Quality Name Prefixes are for organizations that plan to define Quality Names with their own organization-specific prefix (Section 2.3.3) 🌈

The registration policy is Expert Review, as per Section 4.5 of RFC 8126 [BCP26]. The Expert's job is to make sure the organization will handle Quality Names built with their prefix in a way that roughly hits the goals of an IANA registry that supports interoperability of SDF models using these Quality Names 🫢 Those goals are:

  • Stability, "stable and permanent" πŸͺ¨
  • Transparency, "readily available" and "in sufficient detail" (Section 4.6 of RFC 8126 [BCP26]) πŸ”

The "SDF Quality Name Prefixes" registry is empty right now 🫧

7.5.3. sdfType Values πŸŽ€

The "sdfType Values" registry is also part of the "Semantic Definition Format (SDF)" registry group πŸ’– Every entry uses this template:

Name: a name made of lowercase ASCII letters, digits, and - (ASCII hyphen/minus) characters, starting with a lowercase ASCII letter, so the pattern is [a-z][-a-z0-9]*.

Description: a short description of the structure and semantics at the information model level 🧩

type: the value of the quality "type" to be used with this sdfType 🏷️

JSON Representation: a short description of a JSON representation you can use for this sdfType. As per Section 4.7.1, this MUST be consistent with the type. No cap, this one is a must πŸ™ˆ

Reference: a more detailed specification of the meaning and use of sdfType πŸ“š

sdfType values are meant to be registered so you can model extra SDF-specific types (see Section 4.7.1) ✨

The registration policy is Specification Required, as per Section 4.6 of RFC 8126 [BCP26]. The Expert needs to make sure the specification has enough detail for implementations of the sdfType being registered to interoperate. They also need to check that names are specific enough that ecosystem-specific sdfTypes won't get mixed up with the more generally applicable ones πŸ‘‘

The initial set of registrations is described in Table 5 🌸

7.5.4. SDF Feature Names πŸ¦‹

Last one, bestie! The "SDF Feature Names" registry lives in the "Semantic Definition Format (SDF)" registry group too πŸ“ Its template is:

Name: a feature name made of ASCII letters, digits, and dollar signs, starting with a lowercase ASCII letter or a dollar sign, so the pattern is [a-z$][A-Za-z$0-9]*.

Brief Description: a brief description πŸ“

Reference: a pointer to a specification πŸ”—

Change Controller: (See Section 2.3 of RFC 8126 [BCP26]) πŸŽ€

The registration policy is Specification Required, as per Section 4.6 of RFC 8126 [BCP26].

The Experts have two jobs here too:

  • Make sure the specification is available in an immutable reference and has gotten a good level of review.
  • Be frugal when handing out feature names that sound like generally applicable semantics 🌸 Save them for features that are likely to get lots of use and can make good use of being short and sweet ✨

The "SDF Feature Names" registry is empty right now 🫧

Okay, that's the whole registry section, we're officially through the registries πŸŒˆπŸ’…

8. Security Considerations πŸ”’βœ¨

okay bestie, this is the part where we get serious for a sec 🫢 Some of the wider security stuff for Things is covered in [RFC8576], so go read that too.

Section 5 of [RFC8610] goes over the security considerations that pop up when formal description techniques are used to keep interoperability in check. Analogs of those same considerations can apply to SDF 🧠

The security considerations of the underlying building blocks also apply, like the ones in Section 10 of RFC 3629 [STD63]. Those are part of the package too πŸ“¦

SDF uses JSON as its representation language, and for a bunch of cases, [STD90] says that how an implementation behaves with certain constructs the JSON grammar allows is unpredictable. πŸ’€ That's a whole vibe, and not a good one.

Implementations need to be robust against invalid or unpredictable cases on input. The preferred move is to reject input that is invalid or that would lead to unpredictable behavior, and to avoid generating those cases on output too. Just don't be the app that accidentally produces chaos 😭

Implementations of model languages can also run into performance-related availability issues when an attacker controls the input. For a quick look at that, see Section 4.1 of [RFC9535]. For considerations specific to the use of pattern, see Section 8 of [RFC9485] 🌸

SDF may be used in two processes that are often security relevant:

  • (1) model-based _validation_ of data that is meant to be described by SDF models, and
  • (2) model-based _augmentation_ of that data with information obtained from the SDF models that apply.
Both of these are security-sensitive, so treat them with care 🚨

Implementations need to figure out the provenance (and therefore the authenticity and integrity) and the applicability of the SDF models they use operationally in these security-relevant ways. Implementations that use the composition mechanisms defined in this document need to do this for each of the components they combine into the SDF models they employ. Honestly, this needs the same care and scrutiny as bringing any source code into a build environment πŸ› οΈ The possibility of supply-chain attacks on the modules being imported needs to be considered, no cap.

Specifically, implementations might rely on model-based input validation to enforce certain characteristics of the data structure they ingest. If that validation isn't done, it could lead to malfunctions such as crashes and remote code execution πŸ’₯ These implementations need to be extra careful about the data models they apply. That includes their provenance and any potential changes to those characteristics that upgrades to the referenced modules may cause, whether by accident or as part of an attack. Yikes 😱

More generally, implementations should strive to be robust against both expected and unexpected limitations of model-based input validation mechanisms and their implementations. Build in that resilience, bestie πŸ’ͺ

Similarly, implementations that rely on model-based augmentation may generate false data from their inputs. That's an integrity violation in any case, and it can also possibly be exploited for further attacks πŸ₯€

In applications that dynamically acquire models and obtain modules from module references in those models, the security considerations of dereferenceable identifiers apply. See [DEREF-ID-PATTERN] for a more extensive discussion of dereferenceable identifiers πŸ”—

There may be confidentiality requirements on SDF models, both on their content and on the fact that a specific model is used in a particular Thing or environment. 🀫 This specification doesn't discuss model discovery, doesn't define an access control model for SDF models, and doesn't define a way to get selective disclosure where that might be needed. These definitions probably need extra context from the underlying ecosystems and from the characteristics of the protocols used there, so this is left as future work (for example, for documents such as [SDF-MAPPING]) πŸ“šπŸ¦‹


9. References πŸ“–πŸ’–

9.1. Normative References

[BCP26]: Best Current Practice 26, https://www.rfc-editor.org/info/bcp26. At the time of writing, this BCP comprises the following: Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, June 2017, https://www.rfc-editor.org/info/rfc8126.

[BCP73]: Best Current Practice 73, https://www.rfc-editor.org/info/bcp73. At the time of writing, this BCP comprises the following: Mealling, M., Masinter, L., Hardie, T., and G. Klyne, "An IETF URN Sub-namespace for Registered Protocol Parameters", BCP 73, RFC 3553, DOI 10.17487/RFC3553, June 2003, https://www.rfc-editor.org/info/rfc3553.

[IANA.core-parameters]: IANA, "Constrained RESTful Environments (CoRE) Parameters", https://www.iana.org/assignments/core-parameters.

[IANA.media-types]: IANA, "Media Types", https://www.iana.org/assignments/media-types.

[IANA.params]: IANA, "Uniform Resource Name (URN) Namespace for IETF Use", https://www.iana.org/assignments/params.

[IANA.senml]: IANA, "Sensor Measurement Lists (SenML)", https://www.iana.org/assignments/senml.

[RFC2119]: Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, https://www.rfc-editor.org/info/rfc2119.

[RFC3339]: Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002, https://www.rfc-editor.org/info/rfc3339.

[RFC6901]: Bryan, P., Ed., Zyp, K., and M. Nottingham, Ed., "JavaScript Object Notation (JSON) Pointer", RFC 6901, DOI 10.17487/RFC6901, April 2013, https://www.rfc-editor.org/info/rfc6901.

[RFC7396]: Hoffman, P. and J. Snell, "JSON Merge Patch", RFC 7396, DOI 10.17487/RFC7396, October 2014, https://www.rfc-editor.org/info/rfc7396.

[RFC8174]: Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, https://www.rfc-editor.org/info/rfc8174.

[RFC8428]: Jennings, C., Shelby, Z., Arkko, J., Keranen, A., and C. Bormann, "Sensor Measurement Lists (SenML)", RFC 8428, DOI 10.17487/RFC8428, August 2018, https://www.rfc-editor.org/info/rfc8428.

[RFC8610]: Birkholz, H., Vigano, C., and C. Bormann, "Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures", RFC 8610, DOI 10.17487/RFC8610, June 2019, https://www.rfc-editor.org/info/rfc8610.

[RFC8798]: Bormann, C., "Additional Units for Sensor Measurement Lists (SenML)", RFC 8798, DOI 10.17487/RFC8798, June 2020, https://www.rfc-editor.org/info/rfc8798.

[RFC9165]: Bormann, C., "Additional Control Operators for the Concise Data Definition Language (CDDL)", RFC 9165, DOI 10.17487/RFC9165, December 2021, https://www.rfc-editor.org/info/rfc9165.

[RFC9193]: KerΓ€nen, A. and C. Bormann, "Sensor Measurement Lists (SenML) Fields for Indicating Data Value Content-Format", RFC 9193, DOI 10.17487/RFC9193, June 2022, https://www.rfc-editor.org/info/rfc9193.

[RFC9562]: Davis, K., Peabody, B., and P. Leach, "Universally Unique IDentifiers (UUIDs)", RFC 9562, DOI 10.17487/RFC9562, May 2024, https://www.rfc-editor.org/info/rfc9562.

[SPDX]: "SPDX License List", https://spdx.org/licenses/.

[STD63]: Internet Standard 63, https://www.rfc-editor.org/info/std63. At the time of writing, this STD comprises the following: Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, November 2003, https://www.rfc-editor.org/info/rfc3629.

[STD66]: Internet Standard 66, https://www.rfc-editor.org/info/std66. At the time of writing, this STD comprises the following: Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005, https://www.rfc-editor.org/info/rfc3986.

[STD80]: Internet Standard 80, https://www.rfc-editor.org/info/std80. At the time of writing, this STD comprises the following: Cerf, V., "ASCII format for network interchange", STD 80, RFC 20, DOI 10.17487/RFC0020, October 1969, https://www.rfc-editor.org/info/rfc20.

[STD90]: Internet Standard 90, https://www.rfc-editor.org/info/std90. At the time of writing, this STD comprises the following: Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017, https://www.rfc-editor.org/info/rfc8259.

[STD94]: Internet Standard 94, https://www.rfc-editor.org/info/std94. At the time of writing, this STD comprises the following: Bormann, C. and P. Hoffman, "Concise Binary Object Representation (CBOR)", STD 94, RFC 8949, DOI 10.17487/RFC8949, December 2020, https://www.rfc-editor.org/info/rfc8949.

[W3C.NOTE-curie-20101216]: Birbeck, M., Ed. and S. McCarron, Ed., "CURIE Syntax 1.0", W3C Working Group Note, 16 December 2010, https://www.w3.org/TR/2010/NOTE-curie-20101216/.

That's the end of the security stuff and the normative refs, bestie! πŸŒˆπŸ’…

9.2. Informative References ✨

okay bestie, these are the "for more context" links 🧚 they're not required reading, but they're where the good stuff lives. πŸ’–

Appendix A. Formal Syntax of SDF ✨

okay so this one is the normative appendix πŸ’… (aka the official rulebook, no wiggle room) and it describes the syntax of SDF using CDDL [RFC8610]. πŸŽ€

it only shows the framework syntax 🌈, which is a syntax with liberal extension points. ngl that makes it kinda useless for catching typos πŸ’€, so there's also a second one, the validation syntax, which leaves the extension points out. you can make it by deleting every line that contains the string EXTENSION-POINT. it's so trivial that the spec doesn't even print the result πŸ™ˆ

this appendix also uses CDDL "features" from Section 4 of [RFC9165] 🧚. features whose names end in "-ext" are the extension points for future evolution ✨

start = sdf-syntax

sdf-syntax = {
 ; info will be required in most process policies
 ? info: sdfinfo
 ? namespace: named<text>
 ? defaultNamespace: text
 ; Thing is a composition of objects that work together in some way
 ? sdfThing: named<thingqualities>
 ; Object is a set of Properties, Actions, and Events that together
 ; perform a particular function
 ? sdfObject: named<objectqualities>
 ; Includes Properties, Actions, and Events as well as sdfData
 paedataqualities
 * $$SDF-EXTENSION-TOP
 EXTENSION-POINT<"top-ext">
}

sdfinfo = {
 ? title: text
 ? description: text
 ? version: text
 ? copyright: text
 ? license: text
 ? modified: modified-date-time
 ? features: [
               * (any .feature "feature-name") ; EXTENSION-POINT
             ]
 optional-comment
 * $$SDF-EXTENSION-INFO
 EXTENSION-POINT<"info-ext">
}

; Shortcut for a map that gives names to instances of X
; (has keys of type text and values of type X)
named<X> = { * text => X }

; EXTENSION-POINT is only used in framework syntax
EXTENSION-POINT<f> = ( * (quality-name .feature f) => any )
quality-name = text .regexp "([a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*"

sdf-pointer = global / same-object / true
global = text .regexp ".*[:#].*" ; rough CURIE or JSON Pointer syntax
same-object = referenceable-name
referenceable-name = text .regexp "[^:#]*"

; per se no point in having an empty list, but used for sdfRequired
; in odmobject-multiple_axis_joystick.sdf.json
pointer-list = [* sdf-pointer]

optional-comment = (
 ? $comment: text       ; source code comments only, no semantics
)

commonqualities = (
 ? description: text    ; long text (no constraints)
 ? label: text          ; short text (no constraints); default to key
 optional-comment
 ? sdfRef: sdf-pointer
 ; applies to qualities of properties, of data:
 ? sdfRequired: pointer-list
)

arraydefinitionqualities = (
 ? "minItems" => uint
 ? "maxItems" => uint
)

paedataqualities = (
 ; Property represents the state of an instance of an object
 ? sdfProperty: named<propertyqualities>
 ; Action invokes an application layer verb associated with an object
 ? sdfAction: named<actionqualities>
 ; Event represents an occurrence of event associated with an object
 ? sdfEvent: named<eventqualities>
 ; Data represents a piece of information that can be the state of a
 ; property or a parameter to an action or a signal in an event
 ? sdfData: named<dataqualities>

)

; for building hierarchy
thingqualities = {
 commonqualities
 ? sdfObject: named<objectqualities>
 ? sdfThing: named<thingqualities>
 paedataqualities
 arraydefinitionqualities
 * $$SDF-EXTENSION-THING
 EXTENSION-POINT<"thing-ext">
}

; for single objects, or for arrays of objects
objectqualities = {
 commonqualities
 paedataqualities
 arraydefinitionqualities
 * $$SDF-EXTENSION-OBJECT
 EXTENSION-POINT<"object-ext">
}

parameter-list = dataqualities

actionqualities = {
 commonqualities
 ? sdfInputData: parameter-list   ; sdfRequiredInputData applies here
 ? sdfOutputData: parameter-list  ; sdfRequired applies here
 ; zero or more named data type definitions that might be used above
 ? sdfData: named<dataqualities>
 * $$SDF-EXTENSION-ACTION
 EXTENSION-POINT<"action-ext">
}

eventqualities = {
 commonqualities
 ? sdfOutputData: parameter-list  ; sdfRequired applies here
 ; zero or more named data type definitions that might be used above
 ? sdfData: named<dataqualities>
 * $$SDF-EXTENSION-EVENT
 EXTENSION-POINT<"event-ext">
}

sdftype-name = text .regexp "[a-z][-a-z0-9]*"       ; EXTENSION-POINT

dataqualities = {
 commonqualities
 jsonschema
 ? "unit" => text
 ? nullable: bool
 ? "sdfType" => "byte-string" / "unix-time"
            / $SDF-EXTENSION-SDFTYPE .within sdftype-name
            / (sdftype-name .feature "sdftype-ext") ; EXTENSION-POINT
 ? contentFormat: text
 * $$SDF-EXTENSION-DATA
 EXTENSION-POINT<"data-ext">
}

propertyqualities = {
 ? observable: bool
 ? readable: bool
 ? writable: bool
 * $$SDF-EXTENSION-PROPERTY
 ~dataqualities
}

allowed-types = number / text / bool / null
              / [* number] / [* text] / [* bool]
              / {* text => any}
              / $SDF-EXTENSION-ALLOWED
              / (any .feature "allowed-ext")        ; EXTENSION-POINT

compound-type = (
  "type" => "object"
  ? required: [+text]
  ? properties: named<dataqualities>
)

optional-choice = (
 ? (("sdfChoice" => named<dataqualities>)
  // ("enum" => [+ text])) ; limited to text strings
)

jsonschema = (
 ? (("type" => "number" / "string" / "boolean" / "integer" / "array")
    // compound-type
    // $$SDF-EXTENSION-TYPE
    // (type: text .feature "type-ext")             ; EXTENSION-POINT
   )
 ; if present, all other qualities apply to all choices:
 optional-choice
 ; the next three should validate against type:
 ? const: allowed-types
 ? default: allowed-types
 ; number/integer constraints
 ? minimum: number
 ? maximum: number
 ? exclusiveMinimum: number
 ? exclusiveMaximum: number
 ? multipleOf: number
 ; text string constraints
 ? minLength: uint
 ? maxLength: uint
 ? pattern: text                ; regexp
 ? format: "date-time" / "date" / "time"
           / "uri" / "uri-reference" / "uuid"
           / $SDF-EXTENSION-FORMAT .within text
           / (text .feature "format-ext")           ; EXTENSION-POINT
 ; array constraints
 ? minItems: uint
 ? maxItems: uint
 ? uniqueItems: bool
 ? items: jso-items
)

jso-items = {
     ? sdfRef: sdf-pointer ; import limited to subset allowed here...
     ? description: text   ; long text (no constraints)
     optional-comment
     ; leave commonqualities out for non-complex data types,
     ; but need the above three.
     ; no further nesting: no "array"
     ? ((type: "number" / "string" / "boolean" / "integer")
        // compound-type
        // $$SDF-EXTENSION-ITEMTYPE
        // (type: text .feature "itemtype-ext")     ; EXTENSION-POINT
       )
     ; if present, all other qualities apply to all choices
     optional-choice
     ; jso subset
     ? minimum: number
     ? maximum: number
     ? format: text
     ? minLength: uint
     ? maxLength: uint
     * $$SDF-EXTENSION-ITEMS
     EXTENSION-POINT<"items-ext">
   }

modified-date-time = text .abnf modified-dt-abnf
modified-dt-abnf = "modified-dt" .det rfc3339z

; RFC 3339 sans time-numoffset, slightly condensed
rfc3339z = '
   date-fullyear   = 4DIGIT
   date-month      = 2DIGIT  ; 01-12
   date-mday       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on
                             ; month/year
   time-hour       = 2DIGIT  ; 00-23
   time-minute     = 2DIGIT  ; 00-59
   time-second     = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap sec
                             ; rules
   time-secfrac    = "." 1*DIGIT
   DIGIT           =  %x30-39 ; 0-9

   partial-time    = time-hour ":" time-minute ":" time-second
                     [time-secfrac]
   full-date       = date-fullyear "-" date-month "-" date-mday

   modified-dt     = full-date ["T" partial-time "Z"]
'

Appendix B. json-schema.org Rendition of SDF Syntax ✨

this one is informative πŸ¦‹, so it's more of a friendly explainer than the rulebook. it describes the same SDF syntax from Appendix A, but it uses a version of the description techniques advertised on json-schema.org [JSO7] [JSO7V] 🌸

the appendix shows both the validation and the framework syntax πŸ‘‘. since most of the lines are the same between those two files, those lines only show up once, with a leading space, in the form of a unified diff πŸ’œ. lines starting with - belong to the validation syntax, and lines starting with + belong to the framework syntax 🫢

Okay bestie, this part is all schema, so the code just gets copied exactly as written πŸ’…πŸŽ€

    {
   -  "title": "sdf-validation.cddl -- Generated: 2025-10-13T08:43:18Z",
   +  "title": "sdf-framework.cddl -- Generated: 2025-10-13T08:43:29Z",
      "$schema": "http://json-schema.org/draft-07/schema#",
      "$ref": "#/definitions/sdf-syntax",
      "definitions": {
        "sdf-syntax": {
          "type": "object",
          "properties": {
            "info": {
              "$ref": "#/definitions/sdfinfo"
            },
            "namespace": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            },
            "defaultNamespace": {
              "type": "string"
            },
            "sdfThing": {
              "type": "object",
              "additionalProperties": {
                "$ref": "#/definitions/thingqualities"
              }
            },
            "sdfObject": {
              "type": "object",
              "additionalProperties": {
                "$ref": "#/definitions/objectqualities"
              }
            },
            "sdfProperty": {
              "$ref": "#/definitions/sdfProperty-"
            },
            "sdfAction": {
              "$ref": "#/definitions/sdfAction-"
            },
            "sdfEvent": {
              "$ref": "#/definitions/sdfEvent-"
            },
            "sdfData": {
              "$ref": "#/definitions/sdfData-sdfChoice-properties-"
            }
          },
   +      "patternProperties": {
   +        "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +      },
          "additionalProperties": false
        },
        "sdfinfo": {
          "type": "object",
          "properties": {
            "title": {
              "type": "string"
            },
            "description": {
              "type": "string"
            },
            "version": {
              "type": "string"
            },
            "copyright": {
              "type": "string"
            },
            "license": {
              "type": "string"
            },
            "modified": {
              "$ref": "#/definitions/modified-date-time"
            },
            "features": {
   -          "type": "array",
   -          "maxItems": 0
   +          "type": "array"
            },
            "$comment": {
              "type": "string"
            }
          },
   +      "patternProperties": {
   +        "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +      },
          "additionalProperties": false
        },
        "modified-date-time": {
          "type": "string"
        },
        "thingqualities": {
          "type": "object",
          "properties": {
            "description": {
              "type": "string"
            },
            "label": {
              "type": "string"
            },
            "$comment": {
              "type": "string"
            },
            "sdfRef": {
              "$ref": "#/definitions/sdf-pointer"
            },
            "sdfRequired": {
              "$ref": "#/definitions/pointer-list"
            },
            "sdfObject": {
              "type": "object",
              "additionalProperties": {
                "$ref": "#/definitions/objectqualities"
              }
            },
            "sdfThing": {
              "type": "object",
              "additionalProperties": {
                "$ref": "#/definitions/thingqualities"
              }
            },
            "sdfProperty": {
              "$ref": "#/definitions/sdfProperty-"
            },
            "sdfAction": {
              "$ref": "#/definitions/sdfAction-"
            },
            "sdfEvent": {
              "$ref": "#/definitions/sdfEvent-"
            },
            "sdfData": {
              "$ref": "#/definitions/sdfData-sdfChoice-properties-"
            },
            "minItems": {
              "$ref": "#/definitions/uint"
            },
            "maxItems": {
              "$ref": "#/definitions/uint"
            }
          },
   +      "patternProperties": {
   +        "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +      },
          "additionalProperties": false
        },
        "sdf-pointer": {
          "anyOf": [
            {
              "$ref": "#/definitions/global"
            },
            {
              "$ref": "#/definitions/same-object"
            },
            {
              "$ref": "#/definitions/true"
            }
          ]
        },
        "global": {
          "type": "string",
          "pattern": "^[^\\n\\r]*[:#][^\\n\\r]*$"
        },
        "same-object": {
          "$ref": "#/definitions/referenceable-name"
        },
        "referenceable-name": {
          "type": "string",
          "pattern": "^[^:#]*$"
        },
        "true": {
          "type": "boolean",
          "const": true
        },
        "pointer-list": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/sdf-pointer"
          }
        },
        "objectqualities": {
          "type": "object",
          "properties": {
            "description": {
              "type": "string"
            },
            "label": {
              "type": "string"
            },
            "$comment": {
              "type": "string"
            },
            "sdfRef": {
              "$ref": "#/definitions/sdf-pointer"
            },
            "sdfRequired": {
              "$ref": "#/definitions/pointer-list"
            },
            "sdfProperty": {
              "$ref": "#/definitions/sdfProperty-"
            },
            "sdfAction": {
              "$ref": "#/definitions/sdfAction-"
            },
            "sdfEvent": {
              "$ref": "#/definitions/sdfEvent-"
            },
            "sdfData": {
              "$ref": "#/definitions/sdfData-sdfChoice-properties-"
            },
            "minItems": {
              "$ref": "#/definitions/uint"
            },
            "maxItems": {
              "$ref": "#/definitions/uint"
            }
          },
   +      "patternProperties": {
   +        "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +      },
          "additionalProperties": false
        },
        "propertyqualities": {
          "anyOf": [
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "$ref": "#/definitions/type-"
                },
                "sdfChoice": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "observable": {
                  "type": "boolean"
                },
                "readable": {
                  "type": "boolean"
                },
                "writable": {
                  "type": "boolean"
                },
                "description": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "sdfRequired": {
                  "$ref": "#/definitions/pointer-list"
                },
                "const": {
                  "$ref": "#/definitions/allowed-types"
                },
                "default": {
                  "$ref": "#/definitions/allowed-types"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "exclusiveMinimum": {
                  "type": "number"
                },
                "exclusiveMaximum": {
                  "type": "number"
                },
                "multipleOf": {
                  "type": "number"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                },
                "pattern": {
                  "type": "string"
                },
                "format": {
                  "$ref": "#/definitions/format-"
                },
                "minItems": {
                  "$ref": "#/definitions/uint"
                },
                "maxItems": {
                  "$ref": "#/definitions/uint"
                },
                "uniqueItems": {
                  "type": "boolean"
                },
                "items": {
                  "$ref": "#/definitions/jso-items"
                },
                "unit": {
                  "type": "string"
                },
                "nullable": {
                  "type": "boolean"
                },
                "sdfType": {
                  "$ref": "#/definitions/sdfType-"
                },
                "contentFormat": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "type": "string",
                  "const": "object"
                },
                "required": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "properties": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "sdfChoice": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "observable": {
                  "type": "boolean"
                },
                "readable": {
                  "type": "boolean"
                },
                "writable": {
                  "type": "boolean"
                },
                "description": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "sdfRequired": {
                  "$ref": "#/definitions/pointer-list"
                },
                "const": {
                  "$ref": "#/definitions/allowed-types"
                },
                "default": {
                  "$ref": "#/definitions/allowed-types"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "exclusiveMinimum": {
                  "type": "number"
                },
                "exclusiveMaximum": {
                  "type": "number"
                },
                "multipleOf": {
                  "type": "number"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                },
                "pattern": {
                  "type": "string"
                },
                "format": {
                  "$ref": "#/definitions/format-"
                },
                "minItems": {
                  "$ref": "#/definitions/uint"
                },
                "maxItems": {
                  "$ref": "#/definitions/uint"
                },
                "uniqueItems": {
                  "type": "boolean"
                },
                "items": {

okay bestie, we're still in the json-schema rendition, and this bit is a diff πŸ‘€ the lines with a + at the front are the new additions, so you can see exactly what's getting added πŸ’…βœ¨

                  "$ref": "#/definitions/jso-items"
                },
                "unit": {
                  "type": "string"
                },
                "nullable": {
                  "type": "boolean"
                },
                "sdfType": {
                  "$ref": "#/definitions/sdfType-"
                },
                "contentFormat": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
   +          "properties": {
   +            "type": {
   +              "type": "string"
   +            },
   +            "sdfChoice": {
   +              "$ref": "#/definitions/sdfData-sdfChoice-properties-"
   +            },
   +            "observable": {
   +              "type": "boolean"
   +            },
   +            "readable": {
   +              "type": "boolean"
   +            },
   +            "writable": {
   +              "type": "boolean"
   +            },
   +            "description": {
   +              "type": "string"
   +            },
   +            "label": {
   +              "type": "string"
   +            },
   +            "$comment": {
   +              "type": "string"
   +            },
   +            "sdfRef": {
   +              "$ref": "#/definitions/sdf-pointer"
   +            },
   +            "sdfRequired": {
   +              "$ref": "#/definitions/pointer-list"
   +            },
   +            "const": {
   +              "$ref": "#/definitions/allowed-types"
   +            },
   +            "default": {
   +              "$ref": "#/definitions/allowed-types"
   +            },
   +            "minimum": {
   +              "type": "number"
   +            },
   +            "maximum": {
   +              "type": "number"
   +            },
   +            "exclusiveMinimum": {
   +              "type": "number"
   +            },
   +            "exclusiveMaximum": {
   +              "type": "number"
   +            },
   +            "multipleOf": {
   +              "type": "number"
   +            },
   +            "minLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "pattern": {
   +              "type": "string"
   +            },
   +            "format": {
   +              "$ref": "#/definitions/format-"
   +            },
   +            "minItems": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxItems": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "uniqueItems": {
   +              "type": "boolean"
   +            },
   +            "items": {
   +              "$ref": "#/definitions/jso-items"
   +            },
   +            "unit": {
   +              "type": "string"
   +            },
   +            "nullable": {
   +              "type": "boolean"
   +            },
   +            "sdfType": {
   +              "$ref": "#/definitions/sdfType-"
   +            },
   +            "contentFormat": {
   +              "type": "string"
   +            }
   +          },
   +          "additionalProperties": false
   +        },
   +        {
   +          "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "$ref": "#/definitions/type-"
                },
                "enum": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "observable": {
                  "type": "boolean"
                },
                "readable": {
                  "type": "boolean"
                },
                "writable": {
                  "type": "boolean"
                },
                "description": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "sdfRequired": {
                  "$ref": "#/definitions/pointer-list"
                },
                "const": {
                  "$ref": "#/definitions/allowed-types"
                },
                "default": {
                  "$ref": "#/definitions/allowed-types"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "exclusiveMinimum": {
                  "type": "number"
                },
                "exclusiveMaximum": {
                  "type": "number"
                },
                "multipleOf": {
                  "type": "number"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                },
                "pattern": {
                  "type": "string"
                },
                "format": {
                  "$ref": "#/definitions/format-"
                },
                "minItems": {
                  "$ref": "#/definitions/uint"
                },
                "maxItems": {
                  "$ref": "#/definitions/uint"
                },
                "uniqueItems": {
                  "type": "boolean"
                },
                "items": {
                  "$ref": "#/definitions/jso-items"
                },
                "unit": {
                  "type": "string"
                },
                "nullable": {
                  "type": "boolean"
                },
                "sdfType": {
                  "$ref": "#/definitions/sdfType-"
                },
                "contentFormat": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "type": "string",
                  "const": "object"
                },
                "required": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "properties": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "enum": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "observable": {
                  "type": "boolean"
                },
                "readable": {
                  "type": "boolean"
                },
                "writable": {
                  "type": "boolean"
                },
                "description": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "sdfRequired": {
                  "$ref": "#/definitions/pointer-list"
                },
                "const": {
                  "$ref": "#/definitions/allowed-types"
                },
                "default": {
                  "$ref": "#/definitions/allowed-types"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "exclusiveMinimum": {
                  "type": "number"
                },
                "exclusiveMaximum": {
                  "type": "number"
                },
                "multipleOf": {
                  "type": "number"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                },
                "pattern": {
                  "type": "string"
                },
                "format": {
                  "$ref": "#/definitions/format-"
                },
                "minItems": {
                  "$ref": "#/definitions/uint"
                },
                "maxItems": {
                  "$ref": "#/definitions/uint"
                },
                "uniqueItems": {
                  "type": "boolean"
                },
                "items": {
                  "$ref": "#/definitions/jso-items"
                },
                "unit": {
                  "type": "string"
                },
                "nullable": {
                  "type": "boolean"
                },
                "sdfType": {
                  "$ref": "#/definitions/sdfType-"
                },
                "contentFormat": {
                  "type": "string"
                }
              },
              "additionalProperties": false
   +        },
   +        {
   +          "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
   +          "properties": {
   +            "type": {
   +              "type": "string"
   +            },
   +            "enum": {
   +              "type": "array",
   +              "items": {
   +                "type": "string"
   +              },
   +              "minItems": 1
   +            },
   +            "observable": {
   +              "type": "boolean"
   +            },
   +            "readable": {
   +              "type": "boolean"
   +            },
   +            "writable": {
   +              "type": "boolean"
   +            },
   +            "description": {
   +              "type": "string"
   +            },
   +            "label": {
   +              "type": "string"
   +            },
   +            "$comment": {
   +              "type": "string"
   +            },
   +            "sdfRef": {
   +              "$ref": "#/definitions/sdf-pointer"
   +            },
   +            "sdfRequired": {
   +              "$ref": "#/definitions/pointer-list"
   +            },
   +            "const": {
   +              "$ref": "#/definitions/allowed-types"
   +            },
   +            "default": {
   +              "$ref": "#/definitions/allowed-types"
   +            },
   +            "minimum": {
   +              "type": "number"
   +            },
   +            "maximum": {
   +              "type": "number"
   +            },
   +            "exclusiveMinimum": {
   +              "type": "number"
   +            },
   +            "exclusiveMaximum": {
   +              "type": "number"
   +            },
   +            "multipleOf": {
   +              "type": "number"
   +            },
   +            "minLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "pattern": {
   +              "type": "string"
   +            },
   +            "format": {

okay bestie, this part is all json-schema code, and the + lines are the new bits being added πŸ’…βœ¨

   +              "$ref": "#/definitions/format-"
   +            },
   +            "minItems": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxItems": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "uniqueItems": {
   +              "type": "boolean"
   +            },
   +            "items": {
   +              "$ref": "#/definitions/jso-items"
   +            },
   +            "unit": {
   +              "type": "string"
   +            },
   +            "nullable": {
   +              "type": "boolean"
   +            },
   +            "sdfType": {
   +              "$ref": "#/definitions/sdfType-"
   +            },
   +            "contentFormat": {
   +              "type": "string"
   +            }
   +          },
   +          "additionalProperties": false
            }
          ]
        },
        "dataqualities": {
          "anyOf": [
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "$ref": "#/definitions/type-"
                },
                "sdfChoice": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "description": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "sdfRequired": {
                  "$ref": "#/definitions/pointer-list"
                },
                "const": {
                  "$ref": "#/definitions/allowed-types"
                },
                "default": {
                  "$ref": "#/definitions/allowed-types"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "exclusiveMinimum": {
                  "type": "number"
                },
                "exclusiveMaximum": {
                  "type": "number"
                },
                "multipleOf": {
                  "type": "number"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                },
                "pattern": {
                  "type": "string"
                },
                "format": {
                  "$ref": "#/definitions/format-"
                },
                "minItems": {
                  "$ref": "#/definitions/uint"
                },
                "maxItems": {
                  "$ref": "#/definitions/uint"
                },
                "uniqueItems": {
                  "type": "boolean"
                },
                "items": {
                  "$ref": "#/definitions/jso-items"
                },
                "unit": {
                  "type": "string"
                },
                "nullable": {
                  "type": "boolean"
                },
                "sdfType": {
                  "$ref": "#/definitions/sdfType-"
                },
                "contentFormat": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "type": "string",
                  "const": "object"
                },
                "required": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "properties": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "sdfChoice": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "description": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "sdfRequired": {
                  "$ref": "#/definitions/pointer-list"
                },
                "const": {
                  "$ref": "#/definitions/allowed-types"
                },
                "default": {
                  "$ref": "#/definitions/allowed-types"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "exclusiveMinimum": {
                  "type": "number"
                },
                "exclusiveMaximum": {
                  "type": "number"
                },
                "multipleOf": {
                  "type": "number"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                },
                "pattern": {
                  "type": "string"
                },
                "format": {
                  "$ref": "#/definitions/format-"
                },
                "minItems": {
                  "$ref": "#/definitions/uint"
                },
                "maxItems": {
                  "$ref": "#/definitions/uint"
                },
                "uniqueItems": {
                  "type": "boolean"
                },
                "items": {
                  "$ref": "#/definitions/jso-items"
                },
                "unit": {
                  "type": "string"
                },
                "nullable": {
                  "type": "boolean"
                },
                "sdfType": {
                  "$ref": "#/definitions/sdfType-"
                },
                "contentFormat": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
   +          "properties": {
   +            "type": {
   +              "type": "string"
   +            },
   +            "sdfChoice": {
   +              "$ref": "#/definitions/sdfData-sdfChoice-properties-"
   +            },
   +            "description": {
   +              "type": "string"
   +            },
   +            "label": {
   +              "type": "string"
   +            },
   +            "$comment": {
   +              "type": "string"
   +            },
   +            "sdfRef": {
   +              "$ref": "#/definitions/sdf-pointer"
   +            },
   +            "sdfRequired": {
   +              "$ref": "#/definitions/pointer-list"
   +            },
   +            "const": {
   +              "$ref": "#/definitions/allowed-types"
   +            },
   +            "default": {
   +              "$ref": "#/definitions/allowed-types"
   +            },
   +            "minimum": {
   +              "type": "number"
   +            },
   +            "maximum": {
   +              "type": "number"
   +            },
   +            "exclusiveMinimum": {
   +              "type": "number"
   +            },
   +            "exclusiveMaximum": {
   +              "type": "number"
   +            },
   +            "multipleOf": {
   +              "type": "number"
   +            },
   +            "minLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "pattern": {
   +              "type": "string"
   +            },
   +            "format": {
   +              "$ref": "#/definitions/format-"
   +            },
   +            "minItems": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxItems": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "uniqueItems": {
   +              "type": "boolean"
   +            },
   +            "items": {
   +              "$ref": "#/definitions/jso-items"
   +            },
   +            "unit": {
   +              "type": "string"
   +            },
   +            "nullable": {
   +              "type": "boolean"
   +            },
   +            "sdfType": {
   +              "$ref": "#/definitions/sdfType-"
   +            },
   +            "contentFormat": {
   +              "type": "string"
   +            }
   +          },
   +          "additionalProperties": false
   +        },
   +        {
   +          "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "$ref": "#/definitions/type-"
                },
                "enum": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "description": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "sdfRequired": {
                  "$ref": "#/definitions/pointer-list"
                },
                "const": {
                  "$ref": "#/definitions/allowed-types"
                },
                "default": {
                  "$ref": "#/definitions/allowed-types"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "exclusiveMinimum": {
                  "type": "number"
                },
                "exclusiveMaximum": {
                  "type": "number"
                },
                "multipleOf": {
                  "type": "number"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                },
                "pattern": {
                  "type": "string"
                },
                "format": {
                  "$ref": "#/definitions/format-"
                },
                "minItems": {
                  "$ref": "#/definitions/uint"
                },
                "maxItems": {
                  "$ref": "#/definitions/uint"
                },
                "uniqueItems": {
                  "type": "boolean"
                },
                "items": {
                  "$ref": "#/definitions/jso-items"
                },
                "unit": {
                  "type": "string"
                },
                "nullable": {
                  "type": "boolean"
                },
                "sdfType": {
                  "$ref": "#/definitions/sdfType-"
                },
                "contentFormat": {

okay bestie, this part is all schema and no prose, so the code does the talking πŸ’… it's a diff, so the + and - markers stay exactly where they are πŸŽ€

                  "type": "string"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "type": "string",
                  "const": "object"
                },
                "required": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "properties": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "enum": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "description": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "sdfRequired": {
                  "$ref": "#/definitions/pointer-list"
                },
                "const": {
                  "$ref": "#/definitions/allowed-types"
                },
                "default": {
                  "$ref": "#/definitions/allowed-types"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "exclusiveMinimum": {
                  "type": "number"
                },
                "exclusiveMaximum": {
                  "type": "number"
                },
                "multipleOf": {
                  "type": "number"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                },
                "pattern": {
                  "type": "string"
                },
                "format": {
                  "$ref": "#/definitions/format-"
                },
                "minItems": {
                  "$ref": "#/definitions/uint"
                },
                "maxItems": {
                  "$ref": "#/definitions/uint"
                },
                "uniqueItems": {
                  "type": "boolean"
                },
                "items": {
                  "$ref": "#/definitions/jso-items"
                },
                "unit": {
                  "type": "string"
                },
                "nullable": {
                  "type": "boolean"
                },
                "sdfType": {
                  "$ref": "#/definitions/sdfType-"
                },
                "contentFormat": {
                  "type": "string"
                }
              },
              "additionalProperties": false
   +        },
   +        {
   +          "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
   +          "properties": {
   +            "type": {
   +              "type": "string"
   +            },
   +            "enum": {
   +              "type": "array",
   +              "items": {
   +                "type": "string"
   +              },
   +              "minItems": 1
   +            },
   +            "description": {
   +              "type": "string"
   +            },
   +            "label": {
   +              "type": "string"
   +            },
   +            "$comment": {
   +              "type": "string"
   +            },
   +            "sdfRef": {
   +              "$ref": "#/definitions/sdf-pointer"
   +            },
   +            "sdfRequired": {
   +              "$ref": "#/definitions/pointer-list"
   +            },
   +            "const": {
   +              "$ref": "#/definitions/allowed-types"
   +            },
   +            "default": {
   +              "$ref": "#/definitions/allowed-types"
   +            },
   +            "minimum": {
   +              "type": "number"
   +            },
   +            "maximum": {
   +              "type": "number"
   +            },
   +            "exclusiveMinimum": {
   +              "type": "number"
   +            },
   +            "exclusiveMaximum": {
   +              "type": "number"
   +            },
   +            "multipleOf": {
   +              "type": "number"
   +            },
   +            "minLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "pattern": {
   +              "type": "string"
   +            },
   +            "format": {
   +              "$ref": "#/definitions/format-"
   +            },
   +            "minItems": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxItems": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "uniqueItems": {
   +              "type": "boolean"
   +            },
   +            "items": {
   +              "$ref": "#/definitions/jso-items"
   +            },
   +            "unit": {
   +              "type": "string"
   +            },
   +            "nullable": {
   +              "type": "boolean"
   +            },
   +            "sdfType": {
   +              "$ref": "#/definitions/sdfType-"
   +            },
   +            "contentFormat": {
   +              "type": "string"
   +            }
   +          },
   +          "additionalProperties": false
            }
          ]
        },
        "allowed-types": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "type": "string"
            },
            {
              "type": "boolean"
            },
            {
              "type": "null"
            },
            {
              "type": "array",
              "items": {
                "type": "number"
              }
            },
            {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            {
              "type": "array",
              "items": {
                "type": "boolean"
              }
            },
            {
              "type": "object",
              "additionalProperties": {}
   -        }
   +        },
   +        {}
          ]
        },
        "uint": {
          "type": "integer",
          "minimum": 0
        },
        "jso-items": {
          "anyOf": [
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "number",
                    "string",
                    "boolean",
                    "integer"
                  ]
                },
                "sdfChoice": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "description": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "format": {
                  "type": "string"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "type": "string",
                  "const": "object"
                },
                "required": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "properties": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "sdfChoice": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "description": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "format": {
                  "type": "string"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "number",
                    "string",
                    "boolean",
                    "integer"
                  ]
                },
                "enum": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "description": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "format": {
                  "type": "string"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                }
              },
              "additionalProperties": false
            },
            {
              "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
              "properties": {
                "type": {
                  "type": "string",
                  "const": "object"
                },
                "required": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "properties": {
                  "$ref": "#/definitions/sdfData-sdfChoice-properties-"
                },
                "enum": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "minItems": 1
                },
                "sdfRef": {
                  "$ref": "#/definitions/sdf-pointer"
                },
                "description": {
                  "type": "string"
                },
                "$comment": {
                  "type": "string"
                },
                "minimum": {
                  "type": "number"
                },
                "maximum": {
                  "type": "number"
                },
                "format": {
                  "type": "string"
                },
                "minLength": {
                  "$ref": "#/definitions/uint"
                },
                "maxLength": {
                  "$ref": "#/definitions/uint"
                }
              },
              "additionalProperties": false
   +        },
   +        {
   +          "type": "object",
   +          "patternProperties": {
   +            "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +          },
   +          "properties": {
   +            "type": {
   +              "type": "string"
   +            },
   +            "enum": {
   +              "type": "array",
   +              "items": {
   +                "type": "string"
   +              },
   +              "minItems": 1
   +            },
   +            "sdfRef": {
   +              "$ref": "#/definitions/sdf-pointer"
   +            },
   +            "description": {
   +              "type": "string"
   +            },
   +            "$comment": {
   +              "type": "string"
   +            },
   +            "minimum": {
   +              "type": "number"
   +            },
   +            "maximum": {
   +              "type": "number"
   +            },
   +            "format": {
   +              "type": "string"
   +            },
   +            "minLength": {
   +              "$ref": "#/definitions/uint"
   +            },
   +            "maxLength": {
   +              "$ref": "#/definitions/uint"
   +            }
   +          },
   +          "additionalProperties": false
            }
          ]
        },
   +    "sdftype-name": {
   +      "type": "string",
   +      "pattern": "^[a-z][\\-a-z0-9]*$"
   +    },
        "actionqualities": {
          "type": "object",
          "properties": {
            "description": {
              "type": "string"
            },
            "label": {
              "type": "string"
            },
            "$comment": {
              "type": "string"
            },
            "sdfRef": {
              "$ref": "#/definitions/sdf-pointer"
            },
            "sdfRequired": {
              "$ref": "#/definitions/pointer-list"
            },
            "sdfInputData": {
              "$ref": "#/definitions/parameter-list"
            },
            "sdfOutputData": {
              "$ref": "#/definitions/parameter-list"
            },
            "sdfData": {
              "$ref": "#/definitions/sdfData-sdfChoice-properties-"
            }
          },
   +      "patternProperties": {
   +        "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +      },
          "additionalProperties": false
        },
        "parameter-list": {
          "$ref": "#/definitions/dataqualities"
        },
        "eventqualities": {
          "type": "object",
          "properties": {
            "description": {
              "type": "string"
            },
            "label": {
              "type": "string"
            },
            "$comment": {
              "type": "string"
            },
            "sdfRef": {
              "$ref": "#/definitions/sdf-pointer"
            },
            "sdfRequired": {
              "$ref": "#/definitions/pointer-list"
            },
            "sdfOutputData": {
              "$ref": "#/definitions/parameter-list"
            },
            "sdfData": {
              "$ref": "#/definitions/sdfData-sdfChoice-properties-"
            }
          },
   +      "patternProperties": {
   +        "^(?:[a-z][a-z0-9]*:)?[a-z$][A-Za-z$0-9]*$": {}
   +      },
          "additionalProperties": false
        },
        "format-": {
   -      "type": "string",
   -      "enum": [
   -        "date-time",
   -        "date",
   -        "time",
   -        "uri",
   -        "uri-reference",
   -        "uuid"
   +      "anyOf": [
   +        {
   +          "type": "string",
   +          "const": "date-time"
   +        },
   +        {
   +          "type": "string",
   +          "const": "date"
   +        },
   +        {
   +          "type": "string",
   +          "const": "time"
   +        },
   +        {
   +          "type": "string",
   +          "const": "uri"
   +        },
   +        {
   +          "type": "string",
   +          "const": "uri-reference"
   +        },
   +        {
   +          "type": "string",
   +          "const": "uuid"
   +        },
   +        {
   +          "type": "string"
   +        }
   +      ]
   +    },
   +    "sdfType-": {
   +      "anyOf": [
   +        {
   +          "type": "string",
   +          "const": "byte-string"
   +        },
   +        {
   +          "type": "string",
   +          "const": "unix-time"
   +        },
   +        {
   +          "$ref": "#/definitions/sdftype-name"
   +        }
          ]
        },
        "sdfData-sdfChoice-properties-": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/definitions/dataqualities"
          }
        },
        "type-": {
          "type": "string",
          "enum": [
            "number",
            "string",
            "boolean",
            "integer",
            "array"
          ]
        },
   -    "sdfEvent-": {
   +    "sdfProperty-": {
          "type": "object",
          "additionalProperties": {
   -        "$ref": "#/definitions/eventqualities"
   +        "$ref": "#/definitions/propertyqualities"
          }
        },
        "sdfAction-": {
          "type": "object",
          "additionalProperties": {
            "$ref": "#/definitions/actionqualities"
          }
        },
   -    "sdfProperty-": {
   +    "sdfEvent-": {
          "type": "object",
          "additionalProperties": {
   -        "$ref": "#/definitions/propertyqualities"
   +        "$ref": "#/definitions/eventqualities"
          }
   -    },
   -    "sdfType-": {
   -      "type": "string",
   -      "enum": [
   -        "byte-string",
   -        "unix-time"
   -      ]
        }
      }
    }

Appendix C. Data Qualities Inspired by json-schema.org πŸŽ€

This appendix is normative. πŸ’…

Okay bestie, so data qualities are how we describe the data sitting inside SDF affordances, but at the information model level ✨ (not the data model level). A bunch of drafts on json-schema.org (we're calling them JSO here 🫢) describe JSON data at the data model level, and the spec points to [JSO7] and [JSO7V] for a popular version. Since JSON modelers already know JSO's vocabulary, SDF borrows some of those terms and ports their meanings up to the information model level it needs. Main character borrowing energy πŸ’–

The big one is "type". In SDF it can take one of six text string values, which we go through in the subsections below. Heads up: the JSO type "null" is not supported as a value of this data quality in SDF πŸ’€

The additional quality "const" restricts the data to one specific value, the one given as the value of the const quality. Very clean 🧚

The additional quality "default" provides data that can be used in the absence of the data (given as the value of the default quality). Lowkey it's mostly documentary πŸ™ˆ and not very well-defined for SDF, because no process is defined that would add default values to an instance of some interaction data.

Other JSO-inspired qualities are "$comment" and "description", and both of those are also available in the information block 🌸

C.1. type "number", type "integer" πŸ¦‹

The types "number" and "integer" are associated with floating point and integer numbers, as they're available in JSON πŸ”₯. A type value of integer means only integer values of JSON numbers can be used. Be careful here: 10.0 is an integer value, even if it's written in a notation that would also allow non-zero decimal fractions πŸ€“

The additional data qualities "minimum", "maximum", "exclusiveMinimum", and "exclusiveMaximum" provide number values that serve as inclusive/exclusive lower/upper bounds for the number. Note that the Boolean form of "exclusiveMinimum"/"exclusiveMaximum" found in earlier JSO drafts [JSO4V] is not used πŸ™…β€β™€οΈ

The data quality "multipleOf" gives a positive number that constrains the data value to be an integer multiple of the number given. Type "integer" can also be expressed as a "multipleOf" quality of value 1, unless another "multipleOf" quality is present πŸ‘‘

C.2. type "string" ✨

okay so the type "string" is for Unicode text string values, the kind you can write in JSON πŸ’–

The length (counted in characters, specifically Unicode scalar values 🌸) can be constrained with the additional data qualities minLength and maxLength. Both are inclusive bounds, so the endpoints count too πŸŽ€

(More specifically, Unicode text strings as defined in this specification are sequences of Unicode scalar values, and the number of those values is taken as the length of such a text string.

The data quality pattern takes a string value that is interpreted as an [ECMA-262] regular expression in Unicode mode that constrains the string. Note that these are not anchored by default, so unless ^ and $ anchors are employed, ECMA-262 regular expressions match any string that contains a match πŸ‘€ The JSO proposals acknowledge that regular expression support is rather diverse across platforms, so the suggestion is to limit them to:

  • characters;
  • character classes in square brackets, including ranges; their complements;
  • simple quantifiers *, +, ?, and range quantifiers {n}, {n,m}, and {n,};
  • grouping parentheses;
  • the choice operator |;
  • and anchors (beginning-of-input ^ and end-of-input $).

Note that this subset is somewhat similar to the subset introduced by I-Regexps [RFC9485], which are anchored regular expressions and include certain backslash escapes for characters and character classes. It's giving neat little family resemblance 🧚

The additional data quality format can take one of the following values. Note that, at an information model level, the presence of this data quality changes the type from being a simple text string to the abstract meaning of the format given. (i.e., the format "date-time" is less about the specific syntax employed in [RFC3339] than about the usage as an absolute point in civil time). So it's the meaning that matters, not the exact syntax ✨

  • "date-time", "date", "time": A date-time, full-date, or full-time as defined in [RFC3339], respectively.
  • "uri", "uri-reference": A URI or URI Reference as defined in [STD66], respectively.
  • "uuid": A Universally Unique Identifier (UUID) as defined in [RFC9562]).

C.3. type "boolean" πŸ¦‹

The type "boolean" can take the values "true" or "false". Simple, iconic, no notes πŸ’…

C.4. type "array" 🌈

The type "array" is associated with arrays, as they are available in JSON.

The additional quality "items" gives the type that each of the elements of the array must match. (Lowercase "must" here is just the wording, nothing fancy πŸ™ˆ)

The number of elements in the array can be constrained by the additional data qualities "minItems" and "maxItems", which are inclusive bounds.

The additional data quality "uniqueItems" gives a Boolean value that, if true, requires the elements to be all different. So if you want no repeats, this is your girl πŸ’œ

C.5. type "object" πŸ“

The type "object" is associated with maps, from strings to values, as they are available in JSON.

The additional quality "properties" is a map the entries of which describe entries in the specified JSON map: the key gives an allowable map key for the specified JSON map and the value is a map with a named set of data qualities giving the type for the corresponding value in the specified JSON map.

All entries specified in this way are optional unless they are listed in the value of the additional quality "required", which is an array of string values that give the key names of required entries.

Note that the term "properties" as an additional quality for defining map entries is unrelated to sdfProperty. Don't mix them up, bestie, they are not the same thing πŸ™…β€β™€οΈ

For example, to include information about the type of the event in the "overTemperatureEvent" of Figure 4, the sdfOutputData there could be defined as follows:

    "sdfOutputData": {
      "type": "object",
      "properties": {
        "alarmType": {
          "sdfRef": "cap:#/sdfData/alarmTypes/quantityAlarms",
          "const": "OverTemperatureAlarm"
        },
        "temperature": {
   "sdfRef": "#/sdfObject/temperatureWithAlarm/sdfData/temperatureData"
        }
      }
    }

Figure 6: Using Object Type with sdfOutputData πŸŽ€

C.6. Implementation Notes πŸ”₯

JSO-based keywords are also used in the specification techniques of a number of ecosystems, but some adjustments may be required.

For instance, [OCF] is based on Swagger 2.0, which appears to be based on "draft-4" [JSO4] [JSO4V] (also called draft-5, but semantically intended to be equivalent to draft-4). The "exclusiveMinimum" and "exclusiveMaximum" keywords use the Boolean form there, so on import to SDF, their values have to be replaced by the values of the respective "minimum"/"maximum" keyword, which are then removed; the reverse transformation applies on export. Translation drama, but make it make sense 😭

Appendix D. Composition Examples ✨

This informative appendix contains two examples illustrating different composition approaches using the sdfThing quality. Two examples, two vibes 🌸

D.1. Outlet Strip Example πŸ”Œ

{
  "sdfThing": {
    "outlet-strip": {
      "label": "Outlet strip",
      "description": "Contains a number of Sockets",
      "sdfObject": {
        "socket": {
          "description": "An array of sockets in the outlet strip",
          "minItems": 2,
          "maxItems": 10
        }
      }
    }
  }
}

Figure 7: Outlet Strip Example

D.2. Refrigerator-Freezer Example 🧊

{
  "sdfThing": {
    "refrigerator-freezer": {
      "description": "A refrigerator combined with a freezer",
      "sdfProperty": {
        "status": {
          "type": "boolean",
          "description":
"Indicates if the refrigerator-freezer is powered"
        }
      },
      "sdfObject": {
        "refrigerator": {
          "description": "A refrigerator compartment",
          "sdfProperty": {
            "temperature": {
              "sdfRef": "#/sdfProperty/temperature",
              "maximum": 8
            }
          }
        },
        "freezer": {
          "label": "A freezer compartment",
          "sdfProperty": {
            "temperature": {
              "sdfRef": "#/sdfProperty/temperature",
              "maximum": -6
            }
          }
        }
      }
    }
  },
  "sdfProperty": {
    "temperature": {
      "description": "The temperature for this compartment",
      "type": "number",
      "unit": "Cel"
    }
  }
}

Figure 8: Refrigerator-Freezer Example ❄️

Appendix E. Some Changes from Earlier Draft Versions of this Specification πŸ¦‹

This appendix is informative.

The present document provides the base SDF definition. Previous revisions of SDF, as defined in earlier drafts of this specification, have been in use for several years; both significant collections of older SDF models and older SDF conversion tools are available today. This appendix provides a brief checklist that can aid in upgrading these to the standard. So if you have older models lying around, this is your upgrade cheat sheet πŸ’–

  • The quality unit was previously called units.
  • sdfType was developed out of a concept previously called subtype.
  • sdfChoice is the preferred way to represent JSO enum (only a limited form of which is retained) and also the way to represent JSO anyOf.
  • The length of text strings (as used with minLength/maxLength constraints) was previously defined in bytes. It now is defined as the number of characters (Unicode scalar values, to be exact); a length in bytes is not meaningful unless bound to a specific encoding, which might differ from UTF-8 in some ecosystem mappings and protocol bindings. Big plot twist, the unit changed 😭

List of Figures 🌟

Figure 1:  A Simple Example of an SDF Document
Figure 2:  Main Classes Used in SDF Models
Figure 3:  Example sdfObject Definition
Figure 4:  Using sdfRequired
Figure 5:  Using an Override to Further Restrict the Set of Data
           Values
Figure 6:  Using Object Type with sdfOutputData
Figure 7:  Outlet Strip Example
Figure 8:  Refrigerator-Freezer Example

List of Tables πŸ“‹

Table 1:   Qualities of the Information Block
Table 2:   Namespaces Block
Table 3:   Common Qualities
Table 4:   SDF-Defined Qualities of sdfData and sdfProperty
Table 5:   Values Defined in Base SDF for the sdfType Quality
Table 6:   Qualities of sdfObject
Table 7:   Qualities of sdfProperty
Table 8:   Qualities of sdfAction
Table 9:   Qualities of sdfEvent
Table 10:  Qualities of sdfThing
Table 11:  Media Type Registration for SDF
Table 12:  SDF Content-Format Registration
Table 13:  Initial Content of the SDF Quality Names Registry

Acknowledgements 🫢

This specification is based on work by the One Data Model group. Big thanks to them 🌈

Contributors πŸ’œ

Jan Romann
UniversitΓ€t Bremen
Germany
Email: jan.romann@uni-bremen.de


Wouter van der Beek
Cascoda Ltd.
Threefield House
Threefield Lane
Southampton
United Kingdom
Email: w.vanderbeek@cascoda.com

Authors' Addresses ✨

Michael Koster (editor)
KTC Control AB
29415 Alderpoint Road
Blocksburg, CA 95514
United States of America
Phone: +1-707-502-5136
Email: michaeljohnkoster@gmail.com


Carsten Bormann (editor)
UniversitΓ€t Bremen TZI
Postfach 330440
D-28359 Bremen
Germany
Phone: +49-421-218-63921
Email: cabo@tzi.org


Ari KerΓ€nen
Ericsson
FI-02420 Jorvas
Finland
Email: ari.keranen@ericsson.com
πŸ“¦ where this page comes from