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.
https://schemas.inorbit.hr/sensors/consent/v0.jsonEach version is a file at its own address. The raw file is plain JSON, served without a sign-in.
- v0draftMay still be corrected; kept for five minutes.schemas.inorbit.hr/sensors/consent/v0.json ↗
Fields of v0, the newest version, by path. A name ending in [] is each item of a list. Every field not listed is refused.
consent_idanyrequiredcns_ and a ULID (26 Crockford base32 characters), the id prefix and form of every events-service id. Never a bare UUID.
account_idanyrequiredThe account's id, a UUID (an account is not a prefixed resource).
clientanyrequiredWho agreed to send: the kind of client and its own id.
granted_byanyrequiredThe 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 anyrequiredWhich 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 repeatskindslist of anyrequiredWhich kinds of reading it may send: 1 to 3 of gauge, counter, state, each once.
at most 3 itemsno repeatssensor_idslist of anyoptionalOptional, 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 repeatsscopeanyrequiredWhere the readings may be used: the account, or one Decisions space of it.
granted_atanyrequiredWhen the grant was made.
expires_atanyoptionalOptional: 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_atanyoptionalPresent 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_byanyoptionalPresent 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.
notestringoptionalOptional 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 charactersversionintegerrequiredThe 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
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.
Replace the file name with yours.
"json.schemas": [
{ "fileMatch": ["my-file.json"], "url": "https://schemas.inorbit.hr/sensors/consent/v0.json" }
]check-jsonschema --schemafile https://schemas.inorbit.hr/sensors/consent/v0.json my-file.jsonajv validate --spec=draft2020 -s <(curl -s https://schemas.inorbit.hr/sensors/consent/v0.json) -d my-file.jsoncurl -s https://schemas.inorbit.hr/sensors/consent/v0.jsonThe raw file of v0, as published.
{
"$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"
]
}
]
}
}
}