Parsing
parseEnrichedObservation — non-stripping validation of the whole enriched record
parseEnrichedObservation is the validation entry point for any record that may carry slots. One call validates everything; nothing is stripped.
Usage
import { parseEnrichedObservation } from '@disclosureos/schema';
const result = parseEnrichedObservation(record);The result
interface EnrichedParseResult {
success: boolean;
data?: EnrichedObservation; // reference-equal to the input, when success
issues: ValidationIssue[]; // empty iff success
}if (!result.success) {
for (const issue of result.issues) {
console.error(`${issue.path}: ${issue.message}`);
}
// temporal.date: Invalid input
// observableAssessments.technology.warp_drive: unknown observable id
// origin[0].confidence: Too big: expected number to be <=1
// discloureRating: unknown top-level key "discloureRating" — third-party data belongs under "extensions".
}Slot-local issue paths are prefixed with the slot name, so every issue points at the exact field — ready for form mapping or import reports.
What one call checks
- The records core — via records' own
validateObservation. - Every present slot — via the owner package's canonical validator (
validateObservableAssessments,validateOriginClassification), discovered through the registry. Absent slots are simply skipped — a core-only record passes. - Top-level key allowlist — any key that is neither a core property nor a registered slot is rejected with a pointer to the
extensionsbag.
Validation composes by delegation, not by re-parsing through one mega-schema. Each package validates its own slot with its own Zod instance; the contract just orchestrates. This is the same pipeline disclosureos validate runs against files.
Why it never strips
On success the returned data is the same object reference you passed in. There is no .parse() round-trip constructing a new object from recognized keys — which is precisely how ObservationSchema.parse() loses slot data:
// ❌ The hazard this function exists to close:
const stripped = ObservationSchema.parse(enriched);
stripped.origin; // undefined — silently gone
// ✅ The contract way:
const result = parseEnrichedObservation(enriched);
result.data === enriched; // true
result.data?.origin; // intactUnknown keys vs. the extensions bag
The closed root protects against typos and stale field names — the difference between an error today and corrupted data discovered in two years:
parseEnrichedObservation({ ...base, sigthing_quality: 'high' });
// issue: unknown top-level key "sigthing_quality"
parseEnrichedObservation({
...base,
extensions: { myPipeline: { sightingQuality: 'high' } },
});
// ✅ valid — extensions content is yours, passed through untouchedEnrichedObservation
The convenience type for the complete shape — Observation with both slots typed (the package imports both satellites, so the augmentation is active):
import type { EnrichedObservation } from '@disclosureos/schema';
function publish(obs: EnrichedObservation) {
obs.observableAssessments; // typed
obs.origin; // typed
}