Sign in

← All schemas

Schema · sensors/consent

Sensor consent

A record of what one client agreed to share with InOrbit as sensor readings, who agreed, for which account (and optionally one Decisions space), and whether and when that agreement ended. A client is a Mac app, a phone, a browser extension, a Claude plugin, an agent or a runner. It is a record of an agreement, not of data: it holds ids, words from closed lists and timestamps, no reading value, no content and no credential, and the object is closed so a field outside these is refused. A consent is off until a person grants it; a revoke is a fact added to the record (revoked_at and revoked_by), never an edit that erases the grant (ADR 0004). A consent is in force when it is not revoked and has not expired. This is not the OAuth consent of a sign-in (crates/accounts, Hydra): that one lets a client call the API; this one lets a client send readings. The sensor id and timestamp are those of sensors/reading v0, by reference. The sources and kinds are the words of sensors/sensor v0 and the registered sources of the Sensors service (SOURCES in crates/events/src/service/sensors_api.rs), which are the source of truth; they are listed here, and a test pins the list to the service. Draft line: it may still be corrected.

Canonical $id, draft
https://schemas.inorbit.hr/sensors/consent/v0.json
01Versions

Each version is a file at its own address. The raw file is plain JSON, served without a sign-in.

02Fields

Fields of v0, the newest version, by path. A name ending in [] is each item of a list. Every field not listed is refused.

  • account_idanyrequired

    The account's id, a UUID (an account is not a prefixed resource).

  • clientanyrequired

    Who agreed to send: the kind of client and its own id.

  • granted_byanyrequired

    The verified subject that granted it (a person, or a key's own id): a subject id, 1 to 128 characters, no spaces. Never a name or an email address typed into a form.

  • sourceslist of anyrequired

    Which registered sources the client may send from: 1 to 5 of mac, server, agent, connection, phone, each once. A built-in source (monitors, incidents, coordinator, asks) is derived by InOrbit, not sent by a client, and is refused here.

    at most 5 itemsno repeats

  • kindslist of anyrequired

    Which kinds of reading it may send: 1 to 3 of gauge, counter, state, each once.

    at most 3 itemsno repeats

  • sensor_idslist of anyoptional

    Optional, to narrow the grant to named sensors (the per-signal toggles of PRD 0077): at most 64 registered sensor ids, each once. Absent means every sensor of the listed sources and kinds the client registers. A sensor outside the list is not covered, and a new sensor needs a new consent.

    at most 64 itemsno repeats

  • scopeanyrequired

    Where the readings may be used: the account, or one Decisions space of it.

  • granted_atanyrequired

    When the grant was made.

  • expires_atanyoptional

    Optional: when the grant ends by itself. Absent means it holds until it is revoked. The service refuses a time at or before granted_at (a schema cannot compare two fields).

  • revoked_atanyoptional

    Present once the grant is withdrawn: when. Revoking is one call by the same principal or an owner, as short as granting, and takes effect for the next reading: a client that sends after it is refused.

  • revoked_byanyoptional

    Present with revoked_at: the verified subject that withdrew it. It may be the granter, an owner of the account, or the client's own key on a local switch-off.

  • notestringoptional

    Optional short label for the person's own use ("work laptop"): at most 80 characters, no control characters. Not a place for a reason, a name of a third party or a reading. Empty is allowed and means none.

    pattern ^[^\u0000-\u001f\u007f-\u009f]*$at most 80 characters

  • versionintegerrequired

    The version of the terms, 1 or more. A changed table (sources, kinds, sensor_ids or scope) is a new ask and a new version, so a client never sends under terms it was not asked about; a revoke does not change it.

    1 to 1000

03Examples

Valid documents with made-up values, as the repository's tests check them.

{
  "consent_id": "cns_01JBX5N6P9Q3R6S8T0V2W5X7YZ",
  "account_id": "2779ba0b-5d1c-4f7e-9a3b-0c6e1d2f8a41",
  "client": { "kind": "browser_extension", "id": "chrome-ext:7" },
  "granted_by": "usr_nevio",
  "sources": ["agent"],
  "kinds": ["state"],
  "scope": { "level": "account" },
  "granted_at": "2026-10-20T08:00:00.000Z",
  "revoked_at": "2026-10-22T19:30:12.456+02:00",
  "revoked_by": "usr_nevio",
  "version": 1
}

The repository's tests also check 31 documents that this schema must refuse.

04Check a file against it

Replace the file name with yours.

VS Code, JetBrains: settings
"json.schemas": [
  { "fileMatch": ["my-file.json"], "url": "https://schemas.inorbit.hr/sensors/consent/v0.json" }
]
check-jsonschema
check-jsonschema --schemafile https://schemas.inorbit.hr/sensors/consent/v0.json my-file.json
ajv-cli
ajv validate --spec=draft2020 -s <(curl -s https://schemas.inorbit.hr/sensors/consent/v0.json) -d my-file.json
curl
curl -s https://schemas.inorbit.hr/sensors/consent/v0.json
05The schema itself

The raw file of v0, as published.

schemas.inorbit.hr/sensors/consent/v0.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schemas.inorbit.hr/sensors/consent/v0.json",
  "title": "Sensor consent",
  "description": "A record of what one client agreed to share with InOrbit as sensor readings, who agreed, for which account (and optionally one Decisions space), and whether and when that agreement ended. A client is a Mac app, a phone, a browser extension, a Claude plugin, an agent or a runner. It is a record of an agreement, not of data: it holds ids, words from closed lists and timestamps, no reading value, no content and no credential, and the object is closed so a field outside these is refused. A consent is off until a person grants it; a revoke is a fact added to the record (revoked_at and revoked_by), never an edit that erases the grant (ADR 0004). A consent is in force when it is not revoked and has not expired. This is not the OAuth consent of a sign-in (crates/accounts, Hydra): that one lets a client call the API; this one lets a client send readings. The sensor id and timestamp are those of sensors/reading v0, by reference. The sources and kinds are the words of sensors/sensor v0 and the registered sources of the Sensors service (SOURCES in crates/events/src/service/sensors_api.rs), which are the source of truth; they are listed here, and a test pins the list to the service. Draft line: it may still be corrected.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "consent_id",
    "account_id",
    "client",
    "granted_by",
    "sources",
    "kinds",
    "scope",
    "granted_at",
    "version"
  ],
  "properties": {
    "consent_id": {
      "description": "cns_ and a ULID (26 Crockford base32 characters), the id prefix and form of every events-service id. Never a bare UUID.",
      "$ref": "#/$defs/consent_id"
    },
    "account_id": {
      "description": "The account's id, a UUID (an account is not a prefixed resource).",
      "$ref": "#/$defs/uuid"
    },
    "client": {
      "description": "Who agreed to send: the kind of client and its own id.",
      "$ref": "#/$defs/client"
    },
    "granted_by": {
      "description": "The verified subject that granted it (a person, or a key's own id): a subject id, 1 to 128 characters, no spaces. Never a name or an email address typed into a form.",
      "$ref": "#/$defs/subject"
    },
    "sources": {
      "description": "Which registered sources the client may send from: 1 to 5 of mac, server, agent, connection, phone, each once. A built-in source (monitors, incidents, coordinator, asks) is derived by InOrbit, not sent by a client, and is refused here.",
      "type": "array",
      "minItems": 1,
      "maxItems": 5,
      "uniqueItems": true,
      "items": {
        "$ref": "#/$defs/source"
      }
    },
    "kinds": {
      "description": "Which kinds of reading it may send: 1 to 3 of gauge, counter, state, each once.",
      "type": "array",
      "minItems": 1,
      "maxItems": 3,
      "uniqueItems": true,
      "items": {
        "$ref": "#/$defs/kind"
      }
    },
    "sensor_ids": {
      "description": "Optional, to narrow the grant to named sensors (the per-signal toggles of PRD 0077): at most 64 registered sensor ids, each once. Absent means every sensor of the listed sources and kinds the client registers. A sensor outside the list is not covered, and a new sensor needs a new consent.",
      "type": "array",
      "maxItems": 64,
      "uniqueItems": true,
      "items": {
        "$ref": "#/$defs/sensor_id"
      }
    },
    "scope": {
      "description": "Where the readings may be used: the account, or one Decisions space of it.",
      "$ref": "#/$defs/scope"
    },
    "granted_at": {
      "description": "When the grant was made.",
      "$ref": "#/$defs/timestamp"
    },
    "expires_at": {
      "description": "Optional: when the grant ends by itself. Absent means it holds until it is revoked. The service refuses a time at or before granted_at (a schema cannot compare two fields).",
      "$ref": "#/$defs/timestamp"
    },
    "revoked_at": {
      "description": "Present once the grant is withdrawn: when. Revoking is one call by the same principal or an owner, as short as granting, and takes effect for the next reading: a client that sends after it is refused.",
      "$ref": "#/$defs/timestamp"
    },
    "revoked_by": {
      "description": "Present with revoked_at: the verified subject that withdrew it. It may be the granter, an owner of the account, or the client's own key on a local switch-off.",
      "$ref": "#/$defs/subject"
    },
    "note": {
      "description": "Optional short label for the person's own use (\"work laptop\"): at most 80 characters, no control characters. Not a place for a reason, a name of a third party or a reading. Empty is allowed and means none.",
      "type": "string",
      "maxLength": 80,
      "pattern": "^[^\\u0000-\\u001f\\u007f-\\u009f]*$"
    },
    "version": {
      "description": "The version of the terms, 1 or more. A changed table (sources, kinds, sensor_ids or scope) is a new ask and a new version, so a client never sends under terms it was not asked about; a revoke does not change it.",
      "type": "integer",
      "minimum": 1,
      "maximum": 1000
    }
  },
  "dependentRequired": {
    "revoked_at": [
      "revoked_by"
    ],
    "revoked_by": [
      "revoked_at"
    ]
  },
  "$defs": {
    "consent_id": {
      "type": "string",
      "maxLength": 30,
      "pattern": "^cns_[0-9A-HJKMNP-TV-Z]{26}$"
    },
    "uuid": {
      "type": "string",
      "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
    },
    "subject": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "pattern": "^[^\\s\\u0000-\\u001f\\u007f-\\u009f]+$"
    },
    "sensor_id": {
      "description": "A registered sensor's id: a sensors/reading v0 sensor id that starts with sen_. A monitor or a built-in count is not a client's sensor and is refused.",
      "$ref": "https://schemas.inorbit.hr/sensors/reading/v0.json#/$defs/sensor_id",
      "pattern": "^sen_"
    },
    "runner_id": {
      "description": "A runner's id: rnr_ and a ULID (crates/coordinator/src/ids.rs).",
      "type": "string",
      "maxLength": 64,
      "pattern": "^rnr_[0-9A-HJKMNP-TV-Z]{26}$"
    },
    "source": {
      "description": "The registered sources of the Sensors service (SOURCES in sensors_api.rs), the same words as sensors/sensor v0 minus the built-in ones.",
      "enum": [
        "mac",
        "server",
        "agent",
        "connection",
        "phone"
      ]
    },
    "kind": {
      "description": "What a reading holds, the same words as sensors/sensor v0.",
      "enum": [
        "gauge",
        "counter",
        "state"
      ]
    },
    "timestamp": {
      "description": "RFC 3339 with a T and a Z or an offset, at most nine fractional digits: the timestamp of sensors/reading v0.",
      "$ref": "https://schemas.inorbit.hr/sensors/reading/v0.json#/$defs/timestamp"
    },
    "client": {
      "description": "A client is named by its kind and its own id. A runner is named by its runner id; any other kind by the id of the sensor it registered, or by the producer_ref it will register under (1 to 64 of A-Z, a-z, 0-9 and _.:- as in sensors/sensor v0) before it has one.",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "kind",
        "id"
      ],
      "properties": {
        "kind": {
          "description": "mac_app, phone, browser_extension, claude_plugin, agent or runner.",
          "enum": [
            "mac_app",
            "phone",
            "browser_extension",
            "claude_plugin",
            "agent",
            "runner"
          ]
        },
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "pattern": "^[A-Za-z0-9_.:-]{1,64}$"
        }
      },
      "if": {
        "properties": {
          "kind": {
            "const": "runner"
          }
        },
        "required": [
          "kind"
        ]
      },
      "then": {
        "properties": {
          "id": {
            "$ref": "#/$defs/runner_id"
          }
        }
      }
    },
    "scope": {
      "description": "The account (no space_id) or one Decisions space of it (space_id, a UUID).",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "level"
      ],
      "properties": {
        "level": {
          "description": "account or space.",
          "enum": [
            "account",
            "space"
          ]
        },
        "space_id": {
          "$ref": "#/$defs/uuid"
        }
      },
      "oneOf": [
        {
          "properties": {
            "level": {
              "const": "account"
            }
          },
          "not": {
            "required": [
              "space_id"
            ]
          }
        },
        {
          "properties": {
            "level": {
              "const": "space"
            }
          },
          "required": [
            "space_id"
          ]
        }
      ]
    }
  }
}