Tour of the API
The mental model: one Observation, augmented slots, claims, and companions
Every package in DisclosureOS follows the same design. Once you understand four ideas — the Observation, slots, claims, and companions — you can predict how the entire API behaves.
The mental model
1. One record: Observation
The Observation type from @disclosureos/records is the unit of the entire standard. Everything else describes, enriches, validates, or measures it. It is a plain JSON-serializable object — no classes, no methods — so it survives databases, APIs, and language boundaries.
2. Slots: layers augment, never fork
Observables and Origins don't define their own record types. They add slots to Observation via TypeScript module augmentation:
import '@disclosureos/observables'; // Observation now has observableAssessments?
import '@disclosureos/origins'; // Observation now has origin?The record stays one object. A tool that only knows records can still read, store, and forward an enriched observation — the slots ride along as data.
3. Claims: every assessment cites its evidence
Wherever the standard records an assessment — "this showed instantaneous acceleration", "this is best explained by X" — it uses a Claim: who evaluated, when, why, and pointers to the in-record evidence that justifies it.
import { evidenceRef } from '@disclosureos/records/shared';
createObservableClaim('measured', {
confidence: 0.7,
rationale: 'Two independent radar tracks with consistent kinematics.',
evaluatedBy: 'example-institution',
evidenceRefs: [evidenceRef('sensor', 'radar-01')],
});Slots hold arrays of claims. Evaluators disagree; the standard records the disagreement instead of resolving it prematurely. The scoring layer decides what disagreement means (the contested flag) — the record just keeps the facts.
4. Companions: every enum brings its toolkit
Each package ships the same five companions for its vocabulary — constants, guards, factories, formatters, and labels. If a TimeOfDay type exists, then TIMES_OF_DAY, isTimeOfDay, and TIME_OF_DAY_LABELS exist too. Read The Companion Pattern for the full convention.
Validation entry points
There are exactly two correct ways to validate, depending on what you hold:
| You have | Use | From |
|---|---|---|
| A core record (no slots) | validateObservation(obs) → ValidationIssue[] | @disclosureos/records |
| An enriched record (any slots) | parseEnrichedObservation(obs) → { success, data, issues } | @disclosureos/schema |
Slot-specific validators also exist (validateObservableAssessments, validateOriginClassification) for validating a slot value in isolation.
Never ObservationSchema.parse() an enriched record
Zod parsing strips unknown keys — including the slots. Type Safety & Validation explains the strip hazard in detail.
All validators return the same ValidationIssue shape — { path, message } — so error handling is uniform across every layer.
When to use which layer
| Goal | Reach for |
|---|---|
| Describe an observation: time, place, object, witnesses, sensors | records |
| Assert anomalous characteristics with evidence | observables |
| Classify candidate explanations | origins |
| Measure documentation coverage or case strength | scoring |
| Validate a full enriched record, or export JSON Schema | schema |
| Validate files, scaffold templates, browse the registry from a terminal | cli |
See it all at once
The golden path walks one real case — the USS Nimitz encounter — through every layer in about 60 lines of code.