Observation
The core record type — fields, domains, and the extension seam
Observation is the single source of truth for the record lexicon. It's a plain JSON-serializable object validated by ObservationSchema.
Required fields
| Field | Type | Notes |
|---|---|---|
id | string | Stable unique identifier |
temporal | TemporalData | Date (with certainty), optional time, duration |
location | LocationData | Name, country, coordinates, site type |
status | PublicationStatus | draft · review · published · archived · retracted |
createdAt / updatedAt | string | ISO timestamps |
Everything else is optional — a sparse record is a valid record. Completeness scoring measures how much of the lexicon a record fills in; validity never depends on it.
Descriptive domains
| Field | Describes |
|---|---|
summary / description | Short and long prose accounts |
objectCharacteristics | Shape, size, color, luminosity, number observed |
movement | Maneuvers, speed, acceleration, departure |
witnesses | Count, categories, military/independent flags |
sensorEvidence | Sensor readings — each with an id claims can cite |
investigation | Status, investigator, findings |
responseImpact | Interception response, physical effects |
environment | Weather, visibility, terrain |
aviation | Airspace class, nearby facilities |
relations | Typed edges to other observations (RelationEdge) |
sourceData | Where the record came from: source type, credibility |
media / featuredMedia | Attachments — each with an id claims can cite |
eventType | Free-text descriptor (the word classification is reserved for origins) |
import type { Observation } from '@disclosureos/records/observation';
import { createObservation } from '@disclosureos/records/factories';createObservation fills in id, status, and timestamps, then parses — invalid input throws. See Composing an Observation.
The extension seam
Observation carries an empty interface that other packages augment:
export interface ObservationExtensions {}
export type Observation = z.infer<typeof ObservationSchema> & ObservationExtensions;Importing @disclosureos/observables adds the observableAssessments slot; importing @disclosureos/origins adds origin. The slots are typed at compile time but are not part of ObservationSchema — which is why enriched records must be validated with parseEnrichedObservation, never ObservationSchema.parse().
Two more escape hatches exist for data outside the standard:
extensions— a free-formRecord<string, unknown>bag for your own pipeline metadata. Passed through validation untouched.schemaVersion/dataSourceId— clerical metadata for migrations and ingestion tracking.
Records-owned extension slots
Five optional forensic surfaces ship inside records itself (subpath-only schemas, plain optional fields on the record): provenance, identifiers, testimony, physicalEvidence, and documents. See Extension slots.
Validating
import { validateObservation } from '@disclosureos/records';
const issues = validateObservation(record);
// ValidationIssue[] — [{ path: 'location.latitude', message: '...' }]validateObservation accepts unknown, never mutates, and never strips. For records carrying slots, use the schema contract instead.