The Companion Pattern
Deep dive into the five companion exports: constants, guards, factories, formatters, and labels. One pattern across every layer.
Every vocabulary in DisclosureOS follows the same pattern: a type plus five kinds of companions. Learning this once lets you use the entire API — the pattern holds in records, observables, and origins alike.
The five companions
| Companion | Purpose | Example |
|---|---|---|
| Constants | Valid values as arrays | TIMES_OF_DAY → ['dawn', 'morning', ...] |
| Guards | Runtime type narrowing | isTimeOfDay(value) → value is TimeOfDay |
| Factories | Create with defaults + validation | createTemporalData({ date: '2004-11-14' }) |
| Formatters | Human-readable strings | formatTemporalData(temporal) |
| Labels | Value → display name | TIME_OF_DAY_LABELS['morning'] → "Morning" |
Worked example: TimeOfDay
import {
TIMES_OF_DAY,
TIME_OF_DAY_LABELS,
isTimeOfDay,
createTemporalData,
formatTemporalData,
} from '@disclosureos/records';
// Constants — build a dropdown
const options = TIMES_OF_DAY.map((v) => ({
value: v,
label: TIME_OF_DAY_LABELS[v],
}));
// Guard — validate form or API input
const raw: unknown = formData.get('timeOfDay');
const timeOfDay = isTimeOfDay(raw) ? raw : undefined;
// Factory — create a validated temporal object
const temporal = createTemporalData('2004-11-14', 'exact', { timeOfDay });
// Formatter — display in UI
console.log(formatTemporalData(temporal));The same pattern appears for source credibility, object shapes, location sensitivity, observable assessment levels, origin domains, and every other enum in the standard. Constants for options, guards for validation, factories for creation, formatters for display, labels for dropdowns and tables.
Claims have factories too
The pattern extends beyond enums. The evaluation layers ship claim factories that validate on construction:
import { createObservableClaim } from '@disclosureos/observables';
import { createOriginClaim } from '@disclosureos/origins';
const claim = createObservableClaim('measured', { confidence: 0.7 });
// throws if confidence is out of [0, 1]
const hypothesis = createOriginClaim('1.1.3', 0.4, {
rationale: 'Performance exceeds known aerospace capability.',
});
// throws if the OCS node id is unknownFactories parse their output against the schema — what they return is guaranteed valid.
Barrel vs. subpath imports
You can import from the barrel or from subpaths:
// Barrel
import { isTimeOfDay, TIMES_OF_DAY } from '@disclosureos/records';
// Subpaths
import { isTimeOfDay } from '@disclosureos/records/guards';
import { TIMES_OF_DAY } from '@disclosureos/records/constants';
import { TIME_OF_DAY_LABELS } from '@disclosureos/records/labels';Use subpaths when you want tighter imports and only need a subset of the API.
Building custom guards
The shared primitives module exports the helpers the packages use internally, so your own vocabularies can follow the same pattern:
import { createEnumGuard, makeGuard } from '@disclosureos/records/shared';
import { z } from 'zod';
// From a values array
const REVIEW_STATES = ['queued', 'in_review', 'done'] as const;
const isReviewState = createEnumGuard(REVIEW_STATES);
// From any Zod schema
const isPercent = makeGuard(z.number().min(0).max(100));Next steps
- Type Safety & Validation — compile-time types, runtime guards, and where to validate
- Records — Companions — the full companion reference for the core lexicon
- Observables — assessment levels and their companions