@disclosureos/schema 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,11 +4,41 @@ All notable changes to `@disclosureos/schema` are documented here. The format is
4
4
  based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 1.1.0
8
+
9
+ ### Minor Changes
10
+
11
+ - e6ff99c: Add the instruments layer: the UAP Sensor Manifest standard
12
+ (`@disclosureos/instruments@1.0.0`, first published alongside this release —
13
+ see its own changelog).
14
+
15
+ - **`@disclosureos/records`** — `SensorReading` gains optional `sensorRef`
16
+ (`"<org-slug>:<sensor-id>"`, with `sensorRef`/`isSensorRef`/`parseSensorRef`
17
+ helpers in `records/shared`); `SensorType` gains 18 instrument classes
18
+ (infrasonic/ultrasonic sensors, ADS-B receiver, RF spectrum analyzer, passive
19
+ radar, particle detector, atomic clock, GNSS receiver, fluxgate magnetometer,
20
+ air-quality sensor, barometer, lightning detector, IMU, EEG, GSR, pulse
21
+ oximeter, pupillometer, hardware RNG); `DetectionMethod` gains 7 methods
22
+ (particle, gnss, time_reference, chemical, inertial, physiological, pressure).
23
+ Records schema artifact bumped to `1.1.0`.
24
+ - **`@disclosureos/schema`** — composed enriched-observation artifact re-emitted
25
+ at `1.1.0` to carry the records changes.
26
+ - **`@disclosureos/cli`** — new `disclosureos manifest validate <path...>`
27
+ command (`--recursive`, `--json`) delegating to `validateSensorManifest`.
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies [e6ff99c]
32
+ - @disclosureos/records@1.1.0
33
+ - @disclosureos/observables@1.0.1
34
+ - @disclosureos/origins@1.0.1
35
+
7
36
  ## 1.0.0
8
37
 
9
38
  Initial public release — the portable contract.
10
39
 
11
40
  ### Added
41
+
12
42
  - `composeObservationSchema()` — merges the records core and every registered slot
13
43
  schema into one enriched `Observation` JSON Schema, tightening the root to
14
44
  `additionalProperties: false` while preserving the `extensions` bag.
package/README.md CHANGED
@@ -9,7 +9,7 @@ The **portable contract** for DisclosureOS. It composes the slot-bearing package
9
9
  (the `origin` slot) — into one **enriched `Observation`** that means the same thing
10
10
  in TypeScript, JSON Schema, and any other language.
11
11
 
12
- DisclosureOS is a **five-part standard** (records → observables → origins → claims → scoring).
12
+ DisclosureOS is a **six-part standard** (records → observables → origins → claims → scoring → instruments).
13
13
  `schema` is the contract that *binds* records and the package-owned slots into one
14
14
  portable artifact. It is a supporting package, not another part of the standard. (Scoring
15
15
  consumes the enriched record, so it is not part of the composed schema.)
package/dist/index.d.ts CHANGED
@@ -76,7 +76,7 @@ declare const defaultRegistry: ExtensionRegistry;
76
76
  * to the enriched shape — note that bumping a *layer* schema can also change this
77
77
  * artifact, so the drift test guards both.
78
78
  */
79
- declare const ENRICHED_OBSERVATION_SCHEMA_VERSION = "1.0.0";
79
+ declare const ENRICHED_OBSERVATION_SCHEMA_VERSION = "1.1.0";
80
80
  declare const ENRICHED_OBSERVATION_SCHEMA_ID: string;
81
81
  /**
82
82
  * Compose the enriched `Observation` JSON Schema (draft 2020-12) from the records
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ var ExtensionRegistry = class {
40
40
  }
41
41
  };
42
42
  var defaultRegistry = new ExtensionRegistry();
43
- var ENRICHED_OBSERVATION_SCHEMA_VERSION = "1.0.0";
43
+ var ENRICHED_OBSERVATION_SCHEMA_VERSION = "1.1.0";
44
44
  var ENRICHED_OBSERVATION_SCHEMA_ID = schemaId(
45
45
  "schema",
46
46
  "enriched-observation",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry.ts","../src/compose.ts","../src/parse.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `ExtensionRegistry` — the runtime mirror of the TypeScript module augmentation.\n *\n * In TS, importing `@disclosureos/observables` / `@disclosureos/origins` augments\n * `Observation` with the `observableAssessments` / `origin` slots. That augmentation\n * is compile-time only — invisible to JSON Schema, Python, or any non-TS consumer.\n *\n * This registry makes the same fact available at runtime: it maps each satellite\n * slot to its owner package, canonical schema `$id`, version, and a function that\n * emits the slot's standalone JSON Schema. {@link composeObservationSchema} walks\n * the registry to fold every registered slot into one enriched `Observation` schema,\n * so adding a new first-party (or third-party) slot is a single `register()` call —\n * no edit to the composition logic OR the validation logic: {@link composeObservationSchema}\n * walks the registry for JSON Schema and `parseEnrichedObservation` walks it for runtime\n * validation, both driven entirely by what is registered.\n */\nimport type { ValidationIssue } from '@disclosureos/records/shared';\n\n/** One satellite slot on `Observation`, described for runtime composition. */\nexport interface SlotRegistration {\n /** Property name on `Observation` (e.g. `\"observableAssessments\"`, `\"origin\"`). */\n slot: string;\n /** Package that owns the slot (e.g. `\"@disclosureos/observables\"`). */\n owner: string;\n /** Canonical, versioned `$id` of the slot's standalone JSON Schema. */\n schemaId: string;\n /** Schema version of the slot (its `x-schema-version`). */\n version: string;\n /**\n * Emit the slot's standalone JSON Schema (the same enveloped artifact the owner\n * package commits). Called by {@link composeObservationSchema} during merge.\n */\n jsonSchema: () => Record<string, unknown>;\n /**\n * Validate the slot's value with the owner package's canonical, schema-backed\n * validator. Called by `parseEnrichedObservation` for every present slot, so\n * enriched validation stays brand-free (each package validates with its own Zod)\n * and registry-driven (no slot is hard-coded in the parse path).\n */\n validate: (value: unknown) => ValidationIssue[];\n}\n\n/**\n * A mutable map of `slot → registration`. Use the shared {@link defaultRegistry}\n * for the first-party slots; construct your own only for isolated tests or to\n * compose a custom subset of slots.\n */\nexport class ExtensionRegistry {\n private readonly slots = new Map<string, SlotRegistration>();\n\n /**\n * Register a slot. Throws on a duplicate slot name (one owner per slot).\n *\n * @experimental First-party slots (observables, origins) are registered for you\n * on {@link defaultRegistry}. Registering *third-party* slots is supported but the\n * extensibility contract (how external slots compose, version, and resolve) is\n * still settling and may change in a minor release.\n */\n register(registration: SlotRegistration): this {\n if (this.slots.has(registration.slot)) {\n const existing = this.slots.get(registration.slot)!;\n throw new Error(\n `Slot \"${registration.slot}\" is already registered by ${existing.owner}; `\n + `cannot re-register it for ${registration.owner}.`,\n );\n }\n this.slots.set(registration.slot, registration);\n return this;\n }\n\n /** Look up a slot's registration, or `undefined` if not registered. */\n get(slot: string): SlotRegistration | undefined {\n return this.slots.get(slot);\n }\n\n /** Whether a slot is registered. */\n has(slot: string): boolean {\n return this.slots.has(slot);\n }\n\n /** All registrations, in insertion order. */\n list(): SlotRegistration[] {\n return [...this.slots.values()];\n }\n}\n\n/**\n * The shared registry, pre-populated with the first-party satellite slots. Import\n * sites that need the full enriched contract should use this; it is the runtime\n * counterpart to importing both satellite packages for their TS augmentation.\n */\nexport const defaultRegistry = new ExtensionRegistry();\n","/**\n * `composeObservationSchema()` — fold the records core schema and every registered\n * satellite slot schema into one enriched `Observation` JSON Schema.\n *\n * This is the **portable contract**: the single artifact that means the same thing\n * in TypeScript, JSON Schema, Python, and the docs. The records core schema is\n * deliberately OPEN (`additionalProperties: true`) because its satellite slots\n * attach via augmentation; here we fold those slots back in as real properties and\n * TIGHTEN the root to `additionalProperties: false` — unknown top-level keys are\n * rejected, while the `extensions` bag (a named property) still carries arbitrary\n * third-party data.\n *\n * Composition is a brand-free, plain-data merge: each layer's committed JSON Schema\n * (records + the slots from {@link defaultRegistry}) is merged by `$defs` name. The\n * three layers own disjoint `$defs` (records: `Observation`/`Temporal`/…;\n * observables: `ObservableAssessmentMap`/…; origins: `OriginClaim`/…), so the merge\n * needs no `$ref` rewriting; a collision throws loudly rather than silently winning.\n */\nimport { recordsJsonSchema } from '@disclosureos/records';\nimport { schemaId, jsonSchemaEnvelope } from '@disclosureos/records/shared';\nimport {\n observablesJsonSchema,\n validateObservableAssessments,\n OBSERVABLES_SCHEMA_ID,\n OBSERVABLES_SCHEMA_VERSION,\n} from '@disclosureos/observables';\nimport {\n originsJsonSchema,\n validateOriginClassification,\n ORIGINS_SCHEMA_ID,\n ORIGINS_SCHEMA_VERSION,\n} from '@disclosureos/origins';\nimport { defaultRegistry, ExtensionRegistry } from './registry';\n\n/**\n * Version of the emitted composed-schema artifact. Bump on any intentional change\n * to the enriched shape — note that bumping a *layer* schema can also change this\n * artifact, so the drift test guards both.\n */\nexport const ENRICHED_OBSERVATION_SCHEMA_VERSION = '1.0.0';\n\nexport const ENRICHED_OBSERVATION_SCHEMA_ID = schemaId(\n 'schema',\n 'enriched-observation',\n ENRICHED_OBSERVATION_SCHEMA_VERSION,\n);\n\n// Runtime mirror of the TS augmentation: register the first-party slots once, at\n// module load. Importing `@disclosureos/schema` is the runtime counterpart to\n// importing both satellite packages for their compile-time augmentation.\ndefaultRegistry\n .register({\n slot: 'observableAssessments',\n owner: '@disclosureos/observables',\n schemaId: OBSERVABLES_SCHEMA_ID,\n version: OBSERVABLES_SCHEMA_VERSION,\n jsonSchema: observablesJsonSchema,\n validate: validateObservableAssessments,\n })\n .register({\n slot: 'origin',\n owner: '@disclosureos/origins',\n schemaId: ORIGINS_SCHEMA_ID,\n version: ORIGINS_SCHEMA_VERSION,\n jsonSchema: originsJsonSchema,\n validate: validateOriginClassification,\n });\n\n/** Strip the standard envelope (`$schema`/`$id`/`x-schema-version`) and pull `$defs` off a slot schema. */\nfunction splitSlotSchema(schema: Record<string, unknown>): {\n body: Record<string, unknown>;\n defs: Record<string, unknown>;\n} {\n const { $schema, $id, 'x-schema-version': _version, $defs, ...body } = schema as Record<\n string,\n unknown\n > & { $defs?: Record<string, unknown> };\n void $schema;\n void $id;\n void _version;\n return { body, defs: $defs ?? {} };\n}\n\n/**\n * Compose the enriched `Observation` JSON Schema (draft 2020-12) from the records\n * core plus every slot in `registry` (defaults to the first-party\n * {@link defaultRegistry}).\n *\n * The result is the standard enveloped artifact: a `$ref` to `#/$defs/Observation`,\n * where `Observation` carries the records core properties plus one property per\n * registered slot, and `additionalProperties: false` rejects unknown top-level keys.\n */\nexport function composeObservationSchema(\n registry: ExtensionRegistry = defaultRegistry,\n): Record<string, unknown> {\n const records = recordsJsonSchema();\n const defs = structuredClone(records.$defs ?? {}) as Record<string, Record<string, unknown>>;\n\n const observation = defs['Observation'];\n if (!observation) {\n throw new Error('records schema is missing the \"Observation\" $def; cannot compose.');\n }\n\n const slotProperties: Record<string, unknown> = {};\n\n for (const registration of registry.list()) {\n const { body, defs: slotDefs } = splitSlotSchema(registration.jsonSchema());\n\n for (const [name, def] of Object.entries(slotDefs)) {\n if (name in defs) {\n throw new Error(\n `$defs collision composing enriched schema: \"${name}\" is defined by both `\n + `the records core and ${registration.owner}. Slots must own disjoint $defs.`,\n );\n }\n defs[name] = def as Record<string, unknown>;\n }\n\n slotProperties[registration.slot] = body;\n }\n\n // Fold the slots in as real properties and close the root: unknown top-level keys\n // are rejected; the `extensions` bag (a named property) keeps the third-party seam.\n observation['properties'] = {\n ...(observation['properties'] as Record<string, unknown> | undefined),\n ...slotProperties,\n };\n observation['additionalProperties'] = false;\n\n const composed = {\n description:\n 'A complete DisclosureOS observation: the records core enriched with observable '\n + 'assessments and origin classification. The portable contract across TypeScript, '\n + 'JSON Schema, and other languages.',\n $ref: '#/$defs/Observation',\n $defs: defs,\n };\n\n return jsonSchemaEnvelope(composed, {\n id: ENRICHED_OBSERVATION_SCHEMA_ID,\n version: ENRICHED_OBSERVATION_SCHEMA_VERSION,\n });\n}\n","/**\n * `parseEnrichedObservation()` — the non-stripping validation entry point.\n *\n * The records core `ObservationSchema` does not know about the satellite slots, so\n * a raw `ObservationSchema.parse(record)` SILENTLY DROPS `observableAssessments` and\n * `origin` (the A1 \"strip hazard\"). This function closes that hazard at the\n * integration layer: it validates the core and each present slot with the SAME\n * canonical validators each package ships, rejects unknown top-level keys, and\n * returns the caller's object untouched on success — slots intact.\n *\n * It composes by delegation (not by re-parsing through one mega-Zod-object), so it\n * is brand-free: each package validates its own slot with its own Zod instance,\n * exactly as the CLI does.\n */\nimport { validateObservation } from '@disclosureos/records';\nimport type { Observation } from '@disclosureos/records';\nimport type { ValidationIssue } from '@disclosureos/records/shared';\nimport { composeObservationSchema } from './compose';\nimport { defaultRegistry } from './registry';\n\n/**\n * A complete observation: the records core plus the satellite slots. Because both\n * satellite packages augment `Observation`, the slots are already part of the\n * `Observation` type wherever `@disclosureos/schema` (which imports both) is loaded;\n * this alias names that enriched shape explicitly as the public contract type.\n */\nexport type EnrichedObservation = Observation;\n\n/** Outcome of {@link parseEnrichedObservation}: issues are empty iff `success`. */\nexport interface EnrichedParseResult {\n success: boolean;\n /** The validated record (reference-equal to the input) when `success`. */\n data?: EnrichedObservation;\n /** Flat list of validation issues (empty when valid). */\n issues: ValidationIssue[];\n}\n\n/** Prefix slot-local issue paths with the slot name so errors point at the right field. */\nfunction underSlot(slot: string, issues: ValidationIssue[]): ValidationIssue[] {\n return issues.map((issue) => ({\n path: issue.path ? `${slot}.${issue.path}` : slot,\n message: issue.message,\n }));\n}\n\n// The composed schema is the single source of truth for which top-level keys are\n// allowed (records core properties + every registered slot). Computed once.\nlet allowedTopLevelKeys: Set<string> | undefined;\nfunction getAllowedTopLevelKeys(): Set<string> {\n if (!allowedTopLevelKeys) {\n const composed = composeObservationSchema();\n const observation = (composed.$defs as Record<string, Record<string, unknown>>)['Observation'];\n const properties = (observation?.['properties'] as Record<string, unknown>) ?? {};\n allowedTopLevelKeys = new Set(Object.keys(properties));\n }\n return allowedTopLevelKeys;\n}\n\n/**\n * Validate an enriched observation (records core + `observableAssessments` + `origin`)\n * in one call, WITHOUT stripping the satellite slots.\n *\n * Returns `{ success: true, data }` (where `data` is the same object you passed in)\n * when valid, or `{ success: false, issues }` with a flat list of path-tagged issues.\n * Unknown top-level keys are rejected; arbitrary data still belongs under `extensions`.\n */\nexport function parseEnrichedObservation(value: unknown): EnrichedParseResult {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return { success: false, issues: [{ path: '', message: 'expected an observation object' }] };\n }\n\n const record = value as Record<string, unknown>;\n const issues: ValidationIssue[] = [];\n\n issues.push(...validateObservation(record));\n\n // Validate every present slot with its owner's validator — driven by the registry,\n // so a newly registered slot is validated here without editing this function.\n for (const registration of defaultRegistry.list()) {\n const slotValue = record[registration.slot];\n if (slotValue !== undefined) {\n issues.push(...underSlot(registration.slot, registration.validate(slotValue)));\n }\n }\n\n const allowed = getAllowedTopLevelKeys();\n for (const key of Object.keys(record)) {\n if (!allowed.has(key)) {\n issues.push({\n path: key,\n message: `unknown top-level key \"${key}\" — third-party data belongs under \"extensions\".`,\n });\n }\n }\n\n if (issues.length > 0) {\n return { success: false, issues };\n }\n return { success: true, data: record as unknown as EnrichedObservation, issues: [] };\n}\n"]}
1
+ {"version":3,"sources":["../src/registry.ts","../src/compose.ts","../src/parse.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `ExtensionRegistry` — the runtime mirror of the TypeScript module augmentation.\n *\n * In TS, importing `@disclosureos/observables` / `@disclosureos/origins` augments\n * `Observation` with the `observableAssessments` / `origin` slots. That augmentation\n * is compile-time only — invisible to JSON Schema, Python, or any non-TS consumer.\n *\n * This registry makes the same fact available at runtime: it maps each satellite\n * slot to its owner package, canonical schema `$id`, version, and a function that\n * emits the slot's standalone JSON Schema. {@link composeObservationSchema} walks\n * the registry to fold every registered slot into one enriched `Observation` schema,\n * so adding a new first-party (or third-party) slot is a single `register()` call —\n * no edit to the composition logic OR the validation logic: {@link composeObservationSchema}\n * walks the registry for JSON Schema and `parseEnrichedObservation` walks it for runtime\n * validation, both driven entirely by what is registered.\n */\nimport type { ValidationIssue } from '@disclosureos/records/shared';\n\n/** One satellite slot on `Observation`, described for runtime composition. */\nexport interface SlotRegistration {\n /** Property name on `Observation` (e.g. `\"observableAssessments\"`, `\"origin\"`). */\n slot: string;\n /** Package that owns the slot (e.g. `\"@disclosureos/observables\"`). */\n owner: string;\n /** Canonical, versioned `$id` of the slot's standalone JSON Schema. */\n schemaId: string;\n /** Schema version of the slot (its `x-schema-version`). */\n version: string;\n /**\n * Emit the slot's standalone JSON Schema (the same enveloped artifact the owner\n * package commits). Called by {@link composeObservationSchema} during merge.\n */\n jsonSchema: () => Record<string, unknown>;\n /**\n * Validate the slot's value with the owner package's canonical, schema-backed\n * validator. Called by `parseEnrichedObservation` for every present slot, so\n * enriched validation stays brand-free (each package validates with its own Zod)\n * and registry-driven (no slot is hard-coded in the parse path).\n */\n validate: (value: unknown) => ValidationIssue[];\n}\n\n/**\n * A mutable map of `slot → registration`. Use the shared {@link defaultRegistry}\n * for the first-party slots; construct your own only for isolated tests or to\n * compose a custom subset of slots.\n */\nexport class ExtensionRegistry {\n private readonly slots = new Map<string, SlotRegistration>();\n\n /**\n * Register a slot. Throws on a duplicate slot name (one owner per slot).\n *\n * @experimental First-party slots (observables, origins) are registered for you\n * on {@link defaultRegistry}. Registering *third-party* slots is supported but the\n * extensibility contract (how external slots compose, version, and resolve) is\n * still settling and may change in a minor release.\n */\n register(registration: SlotRegistration): this {\n if (this.slots.has(registration.slot)) {\n const existing = this.slots.get(registration.slot)!;\n throw new Error(\n `Slot \"${registration.slot}\" is already registered by ${existing.owner}; `\n + `cannot re-register it for ${registration.owner}.`,\n );\n }\n this.slots.set(registration.slot, registration);\n return this;\n }\n\n /** Look up a slot's registration, or `undefined` if not registered. */\n get(slot: string): SlotRegistration | undefined {\n return this.slots.get(slot);\n }\n\n /** Whether a slot is registered. */\n has(slot: string): boolean {\n return this.slots.has(slot);\n }\n\n /** All registrations, in insertion order. */\n list(): SlotRegistration[] {\n return [...this.slots.values()];\n }\n}\n\n/**\n * The shared registry, pre-populated with the first-party satellite slots. Import\n * sites that need the full enriched contract should use this; it is the runtime\n * counterpart to importing both satellite packages for their TS augmentation.\n */\nexport const defaultRegistry = new ExtensionRegistry();\n","/**\n * `composeObservationSchema()` — fold the records core schema and every registered\n * satellite slot schema into one enriched `Observation` JSON Schema.\n *\n * This is the **portable contract**: the single artifact that means the same thing\n * in TypeScript, JSON Schema, Python, and the docs. The records core schema is\n * deliberately OPEN (`additionalProperties: true`) because its satellite slots\n * attach via augmentation; here we fold those slots back in as real properties and\n * TIGHTEN the root to `additionalProperties: false` — unknown top-level keys are\n * rejected, while the `extensions` bag (a named property) still carries arbitrary\n * third-party data.\n *\n * Composition is a brand-free, plain-data merge: each layer's committed JSON Schema\n * (records + the slots from {@link defaultRegistry}) is merged by `$defs` name. The\n * three layers own disjoint `$defs` (records: `Observation`/`Temporal`/…;\n * observables: `ObservableAssessmentMap`/…; origins: `OriginClaim`/…), so the merge\n * needs no `$ref` rewriting; a collision throws loudly rather than silently winning.\n */\nimport { recordsJsonSchema } from '@disclosureos/records';\nimport { schemaId, jsonSchemaEnvelope } from '@disclosureos/records/shared';\nimport {\n observablesJsonSchema,\n validateObservableAssessments,\n OBSERVABLES_SCHEMA_ID,\n OBSERVABLES_SCHEMA_VERSION,\n} from '@disclosureos/observables';\nimport {\n originsJsonSchema,\n validateOriginClassification,\n ORIGINS_SCHEMA_ID,\n ORIGINS_SCHEMA_VERSION,\n} from '@disclosureos/origins';\nimport { defaultRegistry, ExtensionRegistry } from './registry';\n\n/**\n * Version of the emitted composed-schema artifact. Bump on any intentional change\n * to the enriched shape — note that bumping a *layer* schema can also change this\n * artifact, so the drift test guards both.\n */\nexport const ENRICHED_OBSERVATION_SCHEMA_VERSION = '1.1.0';\n\nexport const ENRICHED_OBSERVATION_SCHEMA_ID = schemaId(\n 'schema',\n 'enriched-observation',\n ENRICHED_OBSERVATION_SCHEMA_VERSION,\n);\n\n// Runtime mirror of the TS augmentation: register the first-party slots once, at\n// module load. Importing `@disclosureos/schema` is the runtime counterpart to\n// importing both satellite packages for their compile-time augmentation.\ndefaultRegistry\n .register({\n slot: 'observableAssessments',\n owner: '@disclosureos/observables',\n schemaId: OBSERVABLES_SCHEMA_ID,\n version: OBSERVABLES_SCHEMA_VERSION,\n jsonSchema: observablesJsonSchema,\n validate: validateObservableAssessments,\n })\n .register({\n slot: 'origin',\n owner: '@disclosureos/origins',\n schemaId: ORIGINS_SCHEMA_ID,\n version: ORIGINS_SCHEMA_VERSION,\n jsonSchema: originsJsonSchema,\n validate: validateOriginClassification,\n });\n\n/** Strip the standard envelope (`$schema`/`$id`/`x-schema-version`) and pull `$defs` off a slot schema. */\nfunction splitSlotSchema(schema: Record<string, unknown>): {\n body: Record<string, unknown>;\n defs: Record<string, unknown>;\n} {\n const { $schema, $id, 'x-schema-version': _version, $defs, ...body } = schema as Record<\n string,\n unknown\n > & { $defs?: Record<string, unknown> };\n void $schema;\n void $id;\n void _version;\n return { body, defs: $defs ?? {} };\n}\n\n/**\n * Compose the enriched `Observation` JSON Schema (draft 2020-12) from the records\n * core plus every slot in `registry` (defaults to the first-party\n * {@link defaultRegistry}).\n *\n * The result is the standard enveloped artifact: a `$ref` to `#/$defs/Observation`,\n * where `Observation` carries the records core properties plus one property per\n * registered slot, and `additionalProperties: false` rejects unknown top-level keys.\n */\nexport function composeObservationSchema(\n registry: ExtensionRegistry = defaultRegistry,\n): Record<string, unknown> {\n const records = recordsJsonSchema();\n const defs = structuredClone(records.$defs ?? {}) as Record<string, Record<string, unknown>>;\n\n const observation = defs['Observation'];\n if (!observation) {\n throw new Error('records schema is missing the \"Observation\" $def; cannot compose.');\n }\n\n const slotProperties: Record<string, unknown> = {};\n\n for (const registration of registry.list()) {\n const { body, defs: slotDefs } = splitSlotSchema(registration.jsonSchema());\n\n for (const [name, def] of Object.entries(slotDefs)) {\n if (name in defs) {\n throw new Error(\n `$defs collision composing enriched schema: \"${name}\" is defined by both `\n + `the records core and ${registration.owner}. Slots must own disjoint $defs.`,\n );\n }\n defs[name] = def as Record<string, unknown>;\n }\n\n slotProperties[registration.slot] = body;\n }\n\n // Fold the slots in as real properties and close the root: unknown top-level keys\n // are rejected; the `extensions` bag (a named property) keeps the third-party seam.\n observation['properties'] = {\n ...(observation['properties'] as Record<string, unknown> | undefined),\n ...slotProperties,\n };\n observation['additionalProperties'] = false;\n\n const composed = {\n description:\n 'A complete DisclosureOS observation: the records core enriched with observable '\n + 'assessments and origin classification. The portable contract across TypeScript, '\n + 'JSON Schema, and other languages.',\n $ref: '#/$defs/Observation',\n $defs: defs,\n };\n\n return jsonSchemaEnvelope(composed, {\n id: ENRICHED_OBSERVATION_SCHEMA_ID,\n version: ENRICHED_OBSERVATION_SCHEMA_VERSION,\n });\n}\n","/**\n * `parseEnrichedObservation()` — the non-stripping validation entry point.\n *\n * The records core `ObservationSchema` does not know about the satellite slots, so\n * a raw `ObservationSchema.parse(record)` SILENTLY DROPS `observableAssessments` and\n * `origin` (the A1 \"strip hazard\"). This function closes that hazard at the\n * integration layer: it validates the core and each present slot with the SAME\n * canonical validators each package ships, rejects unknown top-level keys, and\n * returns the caller's object untouched on success — slots intact.\n *\n * It composes by delegation (not by re-parsing through one mega-Zod-object), so it\n * is brand-free: each package validates its own slot with its own Zod instance,\n * exactly as the CLI does.\n */\nimport { validateObservation } from '@disclosureos/records';\nimport type { Observation } from '@disclosureos/records';\nimport type { ValidationIssue } from '@disclosureos/records/shared';\nimport { composeObservationSchema } from './compose';\nimport { defaultRegistry } from './registry';\n\n/**\n * A complete observation: the records core plus the satellite slots. Because both\n * satellite packages augment `Observation`, the slots are already part of the\n * `Observation` type wherever `@disclosureos/schema` (which imports both) is loaded;\n * this alias names that enriched shape explicitly as the public contract type.\n */\nexport type EnrichedObservation = Observation;\n\n/** Outcome of {@link parseEnrichedObservation}: issues are empty iff `success`. */\nexport interface EnrichedParseResult {\n success: boolean;\n /** The validated record (reference-equal to the input) when `success`. */\n data?: EnrichedObservation;\n /** Flat list of validation issues (empty when valid). */\n issues: ValidationIssue[];\n}\n\n/** Prefix slot-local issue paths with the slot name so errors point at the right field. */\nfunction underSlot(slot: string, issues: ValidationIssue[]): ValidationIssue[] {\n return issues.map((issue) => ({\n path: issue.path ? `${slot}.${issue.path}` : slot,\n message: issue.message,\n }));\n}\n\n// The composed schema is the single source of truth for which top-level keys are\n// allowed (records core properties + every registered slot). Computed once.\nlet allowedTopLevelKeys: Set<string> | undefined;\nfunction getAllowedTopLevelKeys(): Set<string> {\n if (!allowedTopLevelKeys) {\n const composed = composeObservationSchema();\n const observation = (composed.$defs as Record<string, Record<string, unknown>>)['Observation'];\n const properties = (observation?.['properties'] as Record<string, unknown>) ?? {};\n allowedTopLevelKeys = new Set(Object.keys(properties));\n }\n return allowedTopLevelKeys;\n}\n\n/**\n * Validate an enriched observation (records core + `observableAssessments` + `origin`)\n * in one call, WITHOUT stripping the satellite slots.\n *\n * Returns `{ success: true, data }` (where `data` is the same object you passed in)\n * when valid, or `{ success: false, issues }` with a flat list of path-tagged issues.\n * Unknown top-level keys are rejected; arbitrary data still belongs under `extensions`.\n */\nexport function parseEnrichedObservation(value: unknown): EnrichedParseResult {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return { success: false, issues: [{ path: '', message: 'expected an observation object' }] };\n }\n\n const record = value as Record<string, unknown>;\n const issues: ValidationIssue[] = [];\n\n issues.push(...validateObservation(record));\n\n // Validate every present slot with its owner's validator — driven by the registry,\n // so a newly registered slot is validated here without editing this function.\n for (const registration of defaultRegistry.list()) {\n const slotValue = record[registration.slot];\n if (slotValue !== undefined) {\n issues.push(...underSlot(registration.slot, registration.validate(slotValue)));\n }\n }\n\n const allowed = getAllowedTopLevelKeys();\n for (const key of Object.keys(record)) {\n if (!allowed.has(key)) {\n issues.push({\n path: key,\n message: `unknown top-level key \"${key}\" — third-party data belongs under \"extensions\".`,\n });\n }\n }\n\n if (issues.length > 0) {\n return { success: false, issues };\n }\n return { success: true, data: record as unknown as EnrichedObservation, issues: [] };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@disclosureos/schema",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "The DisclosureOS portable contract — composes the records core, observable assessments, and origin classification into one enriched Observation schema (TypeScript + JSON Schema), with a non-stripping parse",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,9 +43,9 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "zod": "^4.4.3",
46
- "@disclosureos/records": "1.0.0",
47
- "@disclosureos/observables": "1.0.0",
48
- "@disclosureos/origins": "1.0.0"
46
+ "@disclosureos/records": "1.1.0",
47
+ "@disclosureos/observables": "1.0.1",
48
+ "@disclosureos/origins": "1.0.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^20",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://os.disclosure.org/schema/schema/1.0.0/enriched-observation.json",
4
- "x-schema-version": "1.0.0",
3
+ "$id": "https://os.disclosure.org/schema/schema/1.1.0/enriched-observation.json",
4
+ "x-schema-version": "1.1.0",
5
5
  "description": "A complete DisclosureOS observation: the records core enriched with observable assessments and origin classification. The portable contract across TypeScript, JSON Schema, and other languages.",
6
6
  "$ref": "#/$defs/Observation",
7
7
  "$defs": {
@@ -1073,6 +1073,24 @@
1073
1073
  "security_camera",
1074
1074
  "dashcam",
1075
1075
  "phone_camera",
1076
+ "infrasonic_sensor",
1077
+ "ultrasonic_sensor",
1078
+ "adsb_receiver",
1079
+ "rf_spectrum_analyzer",
1080
+ "passive_radar",
1081
+ "particle_detector",
1082
+ "atomic_clock",
1083
+ "gnss_receiver",
1084
+ "fluxgate_magnetometer",
1085
+ "air_quality_sensor",
1086
+ "barometer",
1087
+ "lightning_detector",
1088
+ "imu",
1089
+ "eeg",
1090
+ "gsr",
1091
+ "pulse_oximeter",
1092
+ "pupillometer",
1093
+ "hardware_rng",
1076
1094
  "other"
1077
1095
  ],
1078
1096
  "description": "Class of sensor that produced a reading."
@@ -1100,12 +1118,23 @@
1100
1118
  "lidar",
1101
1119
  "spectroscopic",
1102
1120
  "radio_frequency",
1121
+ "particle",
1122
+ "gnss",
1123
+ "time_reference",
1124
+ "chemical",
1125
+ "inertial",
1126
+ "physiological",
1127
+ "pressure",
1103
1128
  "multiple",
1104
1129
  "other",
1105
1130
  "unknown"
1106
1131
  ],
1107
1132
  "description": "How a sensor detected the object."
1108
1133
  },
1134
+ "sensorRef": {
1135
+ "description": "Reference to a published sensor-manifest entry (\"<org-slug>:<sensor-id>\", e.g. \"eldaeon:dionysus-passive-radar\") carrying the instrument’s full spec, timing, and calibration provenance.",
1136
+ "type": "string"
1137
+ },
1109
1138
  "platform": {
1110
1139
  "description": "Platform carrying the sensor (e.g. \"USS Princeton\", \"F/A-18F\").",
1111
1140
  "type": "string"