Schema · atlas/observation
Atlas observation
What an observer reported (RFC 0086, ADR 0006): either an observation, a measured or deterministically read fact (tbd_atlas_core::observation::Observation), or an artifact observation, an artefact's bytes named by their digest (ArtifactObservation). The two never mix: an object with an artifact is an artifact observation (id art_), one without is an observation (id obv_ with its subject, predicate, value and authority). An observation is always positive: absence is concluded from coverage, never reported. Ids are written as the API writes every Atlas id, prefix and UUID, never a bare UUID; the predicate is name@version as in the fact schema; a time carries its wall clock, its boot and its clock's uncertainty. Nothing here carries a document's text, a prompt or a credential, and every object is closed so a field outside these is refused. The observer is https://schemas.inorbit.hr/atlas/observer/v0.json. Draft line: it may still be corrected.
https://schemas.inorbit.hr/atlas/observation/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/atlas/observation/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.
idstringrequiredIts id: obv_ and a UUID for an observation, art_ and a UUID for an artifact observation.
pattern ^(obv|art)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$at most 40 charactersobserveranyrequiredThe observer that made it: obs_ and a UUID.
classanyrequiredThe observer's class. An observation comes from a direct_sensor, a deterministic_extractor or an external_system; an artifact observation from a direct_sensor or a deterministic_extractor. A model's output is an extraction and a person's answer testimony, never these.
subjectanyoptionalWhat it is about: an entity id, ent_ and a UUID. Observation only.
predicateanyoptionalWhat it says of the subject: the predicate and its vocabulary version, name@version (net.connects_to@1). The name is dotted lowercase parts, at most 64 characters. Observation only.
valueanyoptionalThe object, typed. Observation only.
observedanyrequiredWhen the observer saw it, as precisely as it can say.
artifactanyoptionalWhere the artefact lives and the digest of the bytes read. Artifact observation only.
Valid documents with made-up values, as the repository's tests check them.
{
"id": "art_0192b8a4-5c3e-7d21-9f4a-2b3c4d5e6f70",
"observer": "obs_0192b8a4-5c3e-7d21-9f4a-7c8d9e0f1a2b",
"class": "deterministic_extractor",
"artifact": {
"location": "crates/events/src/service/sensors_api.rs@68e8093a65",
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"observed": {
"wall": "2026-10-23T14:09:27Z",
"boot_id": "build-17",
"clock": {
"source": "unknown",
"uncertainty": {
"secs": 1,
"nanos": 0
}
}
}
}The repository's tests also check 33 documents that this schema must refuse.
Replace the file name with yours.
"json.schemas": [
{ "fileMatch": ["my-file.json"], "url": "https://schemas.inorbit.hr/atlas/observation/v0.json" }
]check-jsonschema --schemafile https://schemas.inorbit.hr/atlas/observation/v0.json my-file.jsonajv validate --spec=draft2020 -s <(curl -s https://schemas.inorbit.hr/atlas/observation/v0.json) -d my-file.jsoncurl -s https://schemas.inorbit.hr/atlas/observation/v0.jsonThe raw file of v0, as published.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.inorbit.hr/atlas/observation/v0.json",
"title": "Atlas observation",
"description": "What an observer reported (RFC 0086, ADR 0006): either an observation, a measured or deterministically read fact (tbd_atlas_core::observation::Observation), or an artifact observation, an artefact's bytes named by their digest (ArtifactObservation). The two never mix: an object with an artifact is an artifact observation (id art_), one without is an observation (id obv_ with its subject, predicate, value and authority). An observation is always positive: absence is concluded from coverage, never reported. Ids are written as the API writes every Atlas id, prefix and UUID, never a bare UUID; the predicate is name@version as in the fact schema; a time carries its wall clock, its boot and its clock's uncertainty. Nothing here carries a document's text, a prompt or a credential, and every object is closed so a field outside these is refused. The observer is https://schemas.inorbit.hr/atlas/observer/v0.json. Draft line: it may still be corrected.",
"type": "object",
"additionalProperties": false,
"required": ["id", "observer", "class", "observed"],
"properties": {
"id": {
"description": "Its id: obv_ and a UUID for an observation, art_ and a UUID for an artifact observation.",
"type": "string",
"maxLength": 40,
"pattern": "^(obv|art)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"observer": {
"description": "The observer that made it: obs_ and a UUID.",
"$ref": "https://schemas.inorbit.hr/atlas/observer/v0.json#/$defs/observer_id"
},
"class": {
"description": "The observer's class. An observation comes from a direct_sensor, a deterministic_extractor or an external_system; an artifact observation from a direct_sensor or a deterministic_extractor. A model's output is an extraction and a person's answer testimony, never these.",
"$ref": "https://schemas.inorbit.hr/atlas/observer/v0.json#/$defs/observer_class"
},
"subject": {
"description": "What it is about: an entity id, ent_ and a UUID. Observation only.",
"$ref": "#/$defs/entity_id"
},
"predicate": {
"description": "What it says of the subject: the predicate and its vocabulary version, name@version (net.connects_to@1). The name is dotted lowercase parts, at most 64 characters. Observation only.",
"$ref": "#/$defs/predicate"
},
"value": {
"description": "The object, typed. Observation only.",
"$ref": "#/$defs/value"
},
"observed": {
"description": "When the observer saw it, as precisely as it can say.",
"$ref": "#/$defs/observed_time"
},
"authority": {
"description": "What the observer was allowed to see when it observed. Observation only.",
"$ref": "#/$defs/authority"
},
"artifact": {
"description": "Where the artefact lives and the digest of the bytes read. Artifact observation only.",
"$ref": "#/$defs/artifact_ref"
}
},
"allOf": [
{
"if": {
"required": ["artifact"]
},
"then": {
"properties": {
"id": { "pattern": "^art_" },
"class": { "enum": ["direct_sensor", "deterministic_extractor"] },
"subject": false,
"predicate": false,
"value": false,
"authority": false
}
},
"else": {
"required": ["subject", "predicate", "value", "authority"],
"properties": {
"id": { "pattern": "^obv_" },
"class": { "enum": ["direct_sensor", "deterministic_extractor", "external_system"] }
}
}
}
],
"$defs": {
"entity_id": {
"description": "An entity's id: ent_ and a UUID.",
"type": "string",
"maxLength": 40,
"pattern": "^ent_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"predicate": {
"description": "A predicate and its vocabulary version, name@version.",
"type": "string",
"minLength": 3,
"maxLength": 75,
"pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*@[0-9]{1,10}$"
},
"timestamp": {
"description": "RFC 3339 in UTC or with an offset, at most nine fractional digits. The API writes UTC with milliseconds (2026-10-23T14:09:26.123Z).",
"type": "string",
"minLength": 20,
"maxLength": 40,
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})$"
},
"digest": {
"description": "sha256: and 64 lowercase hex characters. It names bytes; nothing of them travels (ADR 0063).",
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$"
},
"value": {
"description": "The object of an observation, typed (tbd_atlas_core::vocabulary::Value): a truth value, a whole number, text compared exactly, another entity as its id, a digest, or a duration in milliseconds. There is no fractional number: a reading that has one is written as text or scaled by the writer. Short by design: a value names, it does not quote.",
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": ["type", "v"],
"properties": {
"type": { "const": "bool" },
"v": { "type": "boolean" }
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["type", "v"],
"properties": {
"type": { "const": "int" },
"v": { "type": "integer", "minimum": -9223372036854775808, "maximum": 9223372036854775807 }
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["type", "v"],
"properties": {
"type": { "const": "text" },
"v": { "type": "string", "maxLength": 1024 }
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["type", "v"],
"properties": {
"type": { "const": "entity" },
"v": { "$ref": "#/$defs/entity_id" }
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["type", "v"],
"properties": {
"type": { "const": "digest" },
"v": { "$ref": "#/$defs/digest" }
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["type", "v"],
"properties": {
"type": { "const": "millis" },
"v": { "type": "integer", "minimum": 0, "maximum": 18446744073709551615 }
}
}
]
},
"observed_time": {
"description": "When an observer saw something (tbd_atlas_core::time::ObservedTime): the wall clock, the boot it was read in, monotonic nanoseconds since that boot when the observer has them, and the clock with its worst-case error. Two events are ordered only when their uncertainty windows do not overlap.",
"type": "object",
"additionalProperties": false,
"required": ["wall", "boot_id", "clock"],
"properties": {
"wall": {
"description": "The wall-clock reading.",
"$ref": "#/$defs/timestamp"
},
"boot_id": {
"description": "The boot it was read in, 1 to 64 characters, no spaces or control characters.",
"type": "string",
"pattern": "^[^\\s\\u0000-\\u001f\\u007f-\\u009f]{1,64}$"
},
"monotonic_ns": {
"description": "Nanoseconds since that boot; absent or null when the observer has no monotonic clock (the type writes null).",
"type": ["integer", "null"],
"minimum": 0,
"maximum": 18446744073709551615
},
"clock": {
"description": "The clock and its worst-case error, both ways.",
"type": "object",
"additionalProperties": false,
"required": ["source", "uncertainty"],
"properties": {
"source": {
"description": "The clock source: ntp, ptp, rtc, unknown.",
"type": "string",
"pattern": "^[a-z0-9_]{1,32}$"
},
"uncertainty": {
"description": "The worst-case error as whole seconds and nanoseconds, never negative (chrono's TimeDelta, kept as the two numbers it is made of).",
"type": "object",
"additionalProperties": false,
"required": ["secs", "nanos"],
"properties": {
"secs": { "type": "integer", "minimum": 0, "maximum": 9223372036854775 },
"nanos": { "type": "integer", "minimum": 0, "maximum": 999999999 }
}
}
}
}
}
},
"authority": {
"description": "What an observer was allowed to see (tbd_atlas_core::observer::AuthorityContext): who it ran as, what it held, and whether that covered everything in scope. When it did not, absent and not visible to me cannot be told apart.",
"type": "object",
"additionalProperties": false,
"required": ["principal", "capabilities", "sufficient"],
"properties": {
"principal": {
"description": "The identity it ran as (a uid, a service account, an API token's subject): 1 to 128 characters, no spaces or control characters. Never the token itself.",
"type": "string",
"pattern": "^[^\\s\\u0000-\\u001f\\u007f-\\u009f]{1,128}$"
},
"capabilities": {
"description": "The capabilities or scopes it held, sorted, each 1 to 64 of letters, digits and . _ : / * -.",
"type": "array",
"maxItems": 64,
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^[A-Za-z0-9._:/*-]{1,64}$"
}
},
"sufficient": {
"description": "Whether its authority covered everything in scope.",
"type": "boolean"
}
}
},
"artifact_ref": {
"description": "Where an artefact lives and what its bytes were (tbd_atlas_core::observation::ArtifactRef). The location names the artefact (a repository path at a commit, a URL, a document version); the bytes are never here.",
"type": "object",
"additionalProperties": false,
"required": ["location", "digest"],
"properties": {
"location": {
"description": "1 to 1024 characters, no control characters, and no credentials in a URL (user:password@host).",
"type": "string",
"pattern": "^[^\\u0000-\\u001f\\u007f-\\u009f]{1,1024}$",
"not": {
"pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@"
}
},
"digest": {
"description": "The digest of the bytes read, computed from the bytes.",
"$ref": "#/$defs/digest"
}
}
}
}
}