Registry
ExtensionRegistry — the runtime mirror of the TypeScript augmentation
TypeScript module augmentation makes slots typed, but types vanish at runtime and are invisible to JSON Schema, Python, and the CLI. The ExtensionRegistry records the same facts as data: which slots exist, who owns them, how to validate them, and what their JSON Schema is.
The default registry
Importing @disclosureos/schema registers the first-party slots on defaultRegistry — the runtime counterpart of importing both satellite packages for their type augmentation:
import { defaultRegistry } from '@disclosureos/schema';
defaultRegistry.list();
// [
// { slot: 'observableAssessments', owner: '@disclosureos/observables', ... },
// { slot: 'origin', owner: '@disclosureos/origins', ... },
// ]
defaultRegistry.has('origin'); // true
defaultRegistry.get('origin'); // the full registrationWhat a registration carries
interface SlotRegistration {
slot: string; // property name on Observation
owner: string; // owning package
schemaId: string; // canonical, versioned $id of the slot's JSON Schema
version: string; // the slot's x-schema-version
jsonSchema: () => Record<string, unknown>; // standalone JSON Schema artifact
validate: (value: unknown) => ValidationIssue[]; // the owner's canonical validator
}Both consumers of the registry are fully driven by it:
parseEnrichedObservationwalkslist()and calls eachvalidateon present slots.composeObservationSchemawalkslist()and folds eachjsonSchema()into the enriched artifact.
Registering a slot makes it validated and composed — no edits to either code path.
Registering a third-party slot
Experimental
Third-party registration works today, but the extensibility contract — how external slots version, compose, and resolve conflicts — is still settling and may change in a minor release. For most third-party data, the extensions bag is the stable extension area.
import { defaultRegistry } from '@disclosureos/schema';
import { z } from 'zod';
import { validateWith } from '@disclosureos/records/shared';
const AcousticSchema = z.object({
infrasoundDetected: z.boolean(),
peakFrequencyHz: z.number().optional(),
});
defaultRegistry.register({
slot: 'acousticAnalysis',
owner: '@my-org/disclosureos-acoustics',
schemaId: 'https://my-org.example/schema/acoustics/1.0.0/acoustics.json',
version: '1.0.0',
jsonSchema: () => z.toJSONSchema(AcousticSchema, { target: 'draft-2020-12' }),
validate: validateWith(AcousticSchema),
});From that point, parseEnrichedObservation validates acousticAnalysis when present, and composeObservationSchema includes it in the enriched artifact. Pair it with your own module augmentation for compile-time typing:
declare module '@disclosureos/records' {
interface ObservationExtensions {
acousticAnalysis?: z.infer<typeof AcousticSchema>;
}
}Rules enforced by the registry:
- One owner per slot — re-registering an existing slot name throws.
- Disjoint
$defs— composition throws on a$defsname collision rather than silently overwriting.
Custom registries
composeObservationSchema(registry) accepts a custom ExtensionRegistry for composing a subset of slots — useful in tests or when emitting a deliberately narrower contract:
import { ExtensionRegistry, composeObservationSchema } from '@disclosureos/schema';
const coreOnly = new ExtensionRegistry();
const schema = composeObservationSchema(coreOnly); // records core, no slots