Skip to main content

Event

Source fileschemas/event.schema.json
$idurn:synapsecommand:cdm:3.0.0:event
CDM schema version3.0.0
SHA-256 of source527f30670e991bb67df508137e130bba8a7bbf4d035ee5e9a0a8142fd94d7149

Anything that happens. The audit-bearing object: two timestamps and a source, always.

Fields​

FieldTypeRequiredDescription
event_idstring (uuid)yes
event_typeEventTypeyes
geometryPoint | LineString | Polygon | MultiPoint | MultiLineString | MultiPolygon | nullnoGeoJSON, WGS84, [lon, lat] order — e.g. a jamming footprint. Default null.
integrityIntegrity | nullnoPQC signature block — designed, not yet populated. A DATA CONTAINER and nothing more: this package makes no signature and verifies none, no conformance check, harness column or evidence field reads it, and its presence on an object proves nothing about the object. A record carrying one is unverified until something outside this package verifies it. Default null.
object_kind"event"no
observed_atstringyesWhen the SOURCE saw it. Never receipt time. (pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$)
oesOesMetadata | nullnoThe SC-OES wire-semantic block. None = the producer made no SC-OES assertion, which is NOT the producer asserting defaults. Optional so that every object written before SC-OES existed stays structurally valid and every producer that knows nothing about SC-OES keeps emitting objects these models accept. Default null.
payloadobjectnoEvent-specific fields. Validated against PAYLOAD_MODELS[event_type] when one is registered; free-form otherwise. Also the never-drop bag for events.
qualityQuality | nullnoHow good the SOURCE says this object is. None = the source said nothing about quality, which is not the same as saying it is poor. Default null.
received_atstringyesWhen WE took delivery. Never source time. (pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$)
related_entitiesarray<string (uuid)>noentity_id values this event concerns. Empty when the event concerns no specific entity (a feed-level status change).
residualResidual | nullnoSource information the CDM does not model, under the name of the format it came from (§28). None = nothing was left over, or — for the fourteen adapters shipped before this container existed — the leftovers are parked in attributes / payload under source_extras, which ARCHITECTURE.md §5 rules they keep through Part 1. Default null.
schema_versionstringnoSemver of the CDM this object was written against. Default "3.0.0". (pattern ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$)
severitySeverityyes
sourceSourceRefyesWhich adapter produced this object. Required on every kind.
source_idsarray<SourceId>yesEvery external identifier this object is known by. At least one, on EVERY kind — see the class docstring. (min items 1)
statusOperationalStatus | nullnoThe source's own operational state for this object, namespaced. None = the source stated none. Default null.
additionalProperties: false

Unknown keys are rejected. That is safe only because the CDM pairs strictness with a declared escape hatch — Entity.attributes and Event.payload accept anything — so an adapter never has to choose between dropping a field and failing validation.

Referenced definitions​

Every $ref on this page resolves to one of these, inlined here so the page is a complete reference and not a starting point for chasing pointers.

EntityRelation​

What ROLE an entity already related to this event plays in it.

Event.related_entities remains the single answer to "which entities does this event concern"; this adds the role. The membership rule that keeps the two lists agreeing lives on Event (models.py), because it is the only place both lists are visible.

FieldTypeRequiredDescription
entity_idstring (uuid)yesMust also appear in the event's related_entities — see Event._oes_relations.
predicatestringyesAn absolute semantic identifier: a governed ontology term or a valid third-party one. A bare word or a local name is refused. (min length 1)

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

EventClass​

What KIND of assertion an event is — the coarsest semantic distinction SC-OES makes.

Eight members, and a consumer that understands no event type at all can still act on this one. It is asserted by the producer and never inferred: 02-event-classes.md forbids deriving it from type_id, from the payload or from the producer's identity, because the whole value of the field is that somebody took responsibility for the claim.

Closed vocabulary — a value outside this list is invalid, and UNKNOWN is a

member rather than a null wherever the enum has one.

Value
OBSERVATION
STATE_CHANGE
CONSTRAINT
ASSESSMENT
IMPACT
RECOMMENDATION
DECISION
ACTION

EventRelation​

One typed reference from this event to another event.

Both halves are required: a predicate with no target says nothing, and a target with no predicate is the association CORRELATES_WITH exists to spell honestly.

FieldTypeRequiredDescription
event_idstring (uuid)yesThe event this relation points at. It need not be locally available — rejecting an event because its antecedent has not arrived would make delivery order part of the contract.
predicateEventRelationPredicateyesOne of the seven governed predicates. An unrecognised predicate is refused rather than read as a nearby one.

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

EventRelationPredicate​

The seven governed relationship predicates. Direction is part of the meaning.

Five are carried by the LATER event and point at the earlier one; CONTRADICTS and CORRELATES_WITH are conceptually symmetric. An unrecognised predicate is a failure and must not be interpreted as a nearby one — 08-event-relationships.md is explicit, because the nearby predicate is always a stronger claim than the one the producer could support.

Closed vocabulary — a value outside this list is invalid, and UNKNOWN is a

member rather than a null wherever the enum has one.

Value
DERIVED_FROM
UPDATES
SUPERSEDES
RESOLVES
RETRACTS
CONTRADICTS
CORRELATES_WITH

EventType​

Closed vocabulary — a value outside this list is invalid, and UNKNOWN is a

member rather than a null wherever the enum has one.

Value
DETECTION
GNSS_INTERFERENCE
TRACK_UPDATE
ALERT
STATUS_CHANGE
PLAN_INJECT
SIM_RESULT

EvidenceKind​

What an evidence reference points at: another assertion, a source record, or the outside.

Closed vocabulary — a value outside this list is invalid, and UNKNOWN is a

member rather than a null wherever the enum has one.

Value
EVENT
SOURCE_RECORD
EXTERNAL_ARTIFACT

EvidenceRef​

What stands behind an assertion. Descriptive: nothing here is fetched or verified.

One model with a declared kind rather than three, because evidence[] is a heterogeneous list and a discriminated union in the wire form would make a non-Python consumer negotiate a discriminator to read a citation. The per-kind field rules are a validator instead, so the published schema stays readable and the refusal names the kind and the field.

FieldTypeRequiredDescription
descriptionstring | nullnoDefault null. (min length 1)
event_idstring (uuid) | nullnoEVENT only. The cited event need not resolve locally, and a consumer that cannot find it must not reject the citing event for that reason. Default null.
hashstring | nullnoDESCRIPTIVE METADATA ONLY. Not an integrity guarantee, not a signature and not evidence of authenticity; no conformance dimension asserts anything about its value. SC-OES v0.1.0 implements no signing and no verification. Default null. (min length 1)
kindEvidenceKindyesWhich of the three kinds of reference this is.
media_typestring | nullnoDefault null. (min length 1)
source_idSourceId | nullnoSOURCE_RECORD only. The CDM's own source-identifier representation, reused rather than re-invented — never the pair flattened into one opaque string. Default null.
uristring | nullnoEXTERNAL_ARTIFACT only. Never retrieved, at validation time or any other. Default null. (min length 1)

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

Integrity​

DESIGNED, NOT IMPLEMENTED — the field the PQC signature will occupy.

No crypto happens in this package (tests/test_cdm_boundary.py asserts the package imports no crypto module). The field exists from day one so that turning signing on is a value change rather than a schema change: a schema change would be a MAJOR bump rippling through every store and every consumer, and would arrive exactly when the signing work is already late.

algorithm is a free string rather than an enum, naming what the platform's ledger already uses — ML-DSA-87 for entry signatures, SLH-DSA for checkpoints. Free, because the algorithm that replaces those is not knowable now, and an enum would make the migration a MAJOR bump for a value nobody reasons over programmatically.

All three fields or none. A block holding a signature with no algorithm is unverifiable, and an unverifiable signature that LOOKS present is worse than an absent one: it reads as assurance to everything downstream that does not check.

FieldTypeRequiredDescription
algorithmstringyese.g. ML-DSA-87, SLH-DSA-SHAKE-256s. (min length 1)
chain_hashstringyesHash binding this object to the chain. (min length 1)
signaturestringyes(min length 1)

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

LifecycleStatus​

The lifecycle state of the represented CONDITION — not of the message, not of a workflow.

Optional, and absence is the ordinary case: most sensor sources report occurrences rather than managed conditions. 05-lifecycle.md forbids defaulting it to ACTIVE or to anything else, and forbids deriving it from the effective interval — an event whose effective_to has passed is not thereby EXPIRED, because the interval is what the producer said about the world and this is what the producer says about the assertion's own standing.

Closed vocabulary — a value outside this list is invalid, and UNKNOWN is a

member rather than a null wherever the enum has one.

Value
PLANNED
ACTIVE
RESOLVED
EXPIRED
CANCELLED
RETRACTED
SUPERSEDED

LineString​

FieldTypeRequiredDescription
coordinatesarray<array<number>>yes(min items 2)
type"LineString"no

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

MultiLineString​

Several lines that are one thing — a route with a gap, a corridor in two legs.

FieldTypeRequiredDescription
coordinatesarray<array<array<number>>>yes(min items 1)
type"MultiLineString"no

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

MultiPoint​

Several points that are ONE thing — a scatter of detections from one report.

Not a list of Point objects, because a list of geometries is a list of objects and this is one object with a discontinuous location. The difference is visible the moment a consumer counts contacts.

FieldTypeRequiredDescription
coordinatesarray<array<number>>yes(min items 1)
type"MultiPoint"no

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

MultiPolygon​

Several polygons that are one thing — an airspace in disjoint lateral parts.

The ring rules are Polygon's and are enforced per part, for the reason they are enforced there: a ring that arrives open is a source or adapter defect, and closing it invents an edge the source never stated.

FieldTypeRequiredDescription
coordinatesarray<array<array<array<number>>>>yes(min items 1)
type"MultiPolygon"no

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

OesMetadata​

The wire-semantic block. §52's thirteen fields, and maturity is not among them.

Maturity is a property of a governed semantic DEFINITION, not of an occurrence: it lives in the event registry, in ontology-term metadata and in profile documents. Putting it on the wire would duplicate registry governance metadata on every message and give it a second authority, which would then go stale (12-versioning.md).

Three states are distinguished throughout and are not collapsed (01-core.md): a field present with a value is ASSERTED, an absent field or a null confidence is UNKNOWN, and a value whose meaning is "none" is ASSERTED-ABSENT. An unknown value is never represented as a zero, an empty string or a default enumeration member — which is why every optional field here defaults to None and none of the enumerations carries an UNKNOWN member the way the CDM's own vocabularies do (enums.py:3). The CDM's rule is right for a closed structural classification a map has to render; SC-OES's optional fields are assertions a producer either made or did not, and "not asserted" is exactly what absence already says.

FieldTypeRequiredDescription
confidencenumber | nullno0..1 on the producer's own scale. None = unknown, never 0.0, and this specification defines no universal confidence algorithm. Default null. (≥ 0; ≤ 1)
effective_fromstring | nullnoWhen the represented condition begins. NEVER defaulted to observed_at, received_at or the time of validation. Default null. (pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$)
effective_tostring | nullnoWhen it ceases. None = the producer did not state an end; it does not mean the condition is permanent and it does not mean it is still in force. Default null. (pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$)
entity_relationsarray<EntityRelation>noRoles for entities already in related_entities.
event_classEventClassyesAsserted by the producer. Never inferred from type_id or from the payload.
event_relationsarray<EventRelation>noEmpty or absent asserts nothing. No universal cap.
evidencearray<EvidenceRef>noWhat stands behind the assertion. No universal cap.
extensionsobjectnoThe one declared open bag. Keys are x.<namespace>.<name>; sc.* is reserved and rejected in v0.1; values are preserved and never interpreted.
securitySecurityMarking | nullnoTransported markings. Absence is never a conformance failure and is never proof of unclassified status. Default null.
spec_versionstringyesThe SC-OES version whose semantics the producer is claiming. Semver, on the same rule CDMBase applies to schema_version; NOT derived from SCHEMA_VERSION or PACKAGE_VERSION, and not required to equal this package's SC_OES_VERSION — a producer at a later spec version stays transportable.
statusLifecycleStatus | nullnoNone = the source says nothing about lifecycle. Default null.
type_idstringyesThe governed or third-party semantic type identifier, under one of the two frozen grammars. Syntax here; recognition is dimension C's.
verificationVerification | nullnoNone = nothing is asserted about corroboration. Default null.

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

OperationalStatus​

What state the source says a thing is in, in the SOURCE's vocabulary, namespaced.

THERE IS NO ENUM HERE AND THERE WILL NOT BE ONE. "SERVICEABLE", "DEGRADED", "MISSION CAPABLE", "RED" and "U/S" come from five different domains and mean five different things, and a closed CDM vocabulary would have to map each of them onto a member — which is a judgement about somebody else's operational language, made inside a translator, invisible in the output, and exactly what Rule 6 forbids. namespace says whose vocabulary state belongs to, so a consumer meeting an unfamiliar value can find out what it means instead of guessing; a consumer MUST NOT compare state across namespaces.

since is when the state began, not when it was reported. Absent means the source did not say — never the receipt time, which would make every restart look like a state change.

FieldTypeRequiredDescription
attributesobjectnoStatus-specific source fields with no canonical home.
namespacestringyesWhose vocabulary state is in — normally the source format's name. Required: an unnamespaced status is a word with no owner. (min length 1)
sincestring | nullnoWhen the state began. None = the source did not say. Default null. (pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$)
statestringyesThe source's own token, verbatim and untranslated. (min length 1)

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

Point​

FieldTypeRequiredDescription
coordinatesarray<number>yes
type"Point"no

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

Polygon​

FieldTypeRequiredDescription
coordinatesarray<array<array<number>>>yes(min items 1)
type"Polygon"no

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

Quality​

How good the source says its own data is. Four ways of saying it, none derived.

confidence is 0..1 and comparable across sources; source_quality is the source's OWN grade, a free string, because ASTERIX's track quality, AIS's position accuracy flag and a NATO track's evaluation code are three ordinal scales with no defined mapping between them and inventing one would be a fusion decision made inside a translator.

uncertainty is a NAMED dict — {"along_track_m": 40.0, "cross_track_m": 12.0} — rather than a single number, because an error ellipse is not a radius and flattening it loses the orientation that made it worth sending. Names are the source's; the CDM does not fix a vocabulary here, and the unit belongs in the key exactly as signal_strength_dbm carries its own unit in its name.

FieldTypeRequiredDescription
accuracy_mnumber | nullnoMetres, 1-sigma. None = unknown, never 0. Default null. (≥ 0)
confidencenumber | nullno0..1. None = unknown; 0 means certainty-that-not, which is a claim. Default null. (≥ 0; ≤ 1)
source_qualitystring | nullnoThe source's own grade, verbatim. None = the source stated none. Default null. (min length 1)
uncertaintyobject<string, number>noNamed components, e.g. along_track_m / cross_track_m. The unit is in the key. Empty = the source named none.

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

Residual​

Source information the CDM does not model, kept under the name of the format it came from.

§28's container, and its two rules are load-bearing in opposite directions:

  1. It MUST identify its origin. namespace is the source format's own name — the same string as the adapter's metadata.format.name — so a reader meeting an unfamiliar key inside data can find out which standard's vocabulary it belongs to. An unnamespaced bag of leftovers is the free-form dict this model exists to replace.
  2. It MUST NOT be treated as semantically trusted merely because it survived translation. A residual says "the source said this and the CDM has no home for it". It is a fact about the source RECORD, not an assertion about the world. A consumer MUST NOT promote a residual value into a canonical field, and a later adapter MUST NOT read another adapter's residual as an input — ARCHITECTURE.md §5 states both as normative text.

data preserves the source's own STRUCTURE, which is why it is a dict and not a list of dotted paths: lossless.residual() learned that the hard way when a two-element list came back as two keys named affected_constellations[0] and [1], satisfying the never-drop rule in the letter while destroying the reader's ability to see a list.

THE FOURTEEN ADAPTERS IN THIS REPOSITORY DO NOT USE THIS YET, deliberately. ARCHITECTURE.md §5 rules that they keep their attributes / payload parking under source_extras through the whole of Part 1 and declare residual: legacy in their manifests, because the information is already preserved and paying for a placement change with every golden file and every downstream consumer buys nothing a reader can use (§29: no breaking change for stylistic cleanliness). Every Part 2 adapter declares residual: structured and uses this.

FieldTypeRequiredDescription
dataobjectnoThe unconsumed source structure, preserved as the source shaped it.
namespacestringyesThe source format's name — normally metadata.format.name. Required. (min length 1)

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

SecurityMarking​

Handling markings, TRANSPORTED. SC-OES does not interpret or enforce them.

Three propositions 10-security-markings.md states because each is routinely assumed away: presence of a marking is not authorization, absence is not proof of unclassified status, and transport is not enforcement. SC-OES is not a cross-domain guard and must not be represented as one; nothing in this package could enforce a marking, and a deployment that enforces one does so with its own accredited mechanism, which knows the scheme and is answerable for the decision.

Every value here is interpreted ONLY relative to scheme, which is why scheme is the one required field: two markings from different schemes are not comparable, and a classification with no scheme beside it is a string somebody will compare anyway.

FieldTypeRequiredDescription
caveatsarray<string>noAs that scheme spells them.
classificationstring | nullnoAs that scheme spells it. Transported verbatim. Default null. (min length 1)
marking_extrasobject<string, string>noScheme-specific values with no generic counterpart. Strings, because dimension B checks that marking values are strings and because the block has exactly one generic open bag and it is oes.extensions.
originatorstring | nullnoAs that scheme identifies them. Default null. (min length 1)
releasabilityarray<string>noAs that scheme spells them. Never reordered.
schemestringyesWhich marking system these values belong to. Required. (min length 1)

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

Severity​

Closed vocabulary — a value outside this list is invalid, and UNKNOWN is a

member rather than a null wherever the enum has one.

Value
INFO
ADVISORY
WARNING
CRITICAL

SourceHash​

A digest of the source record, as the ADAPTER computed it. Carried, never computed here.

Rule 5 asks for "source hash where appropriate", and the appropriateness is the adapter's judgement: a hash of a 3-byte AIS sentence identifies nothing an external id does not, while a hash of a 4 MB NITF segment is how an auditor proves the bytes on the wire were the bytes described. So it is optional, and its absence means the adapter did not compute one.

NO HASHING HAPPENS IN THIS PACKAGE. tests/test_cdm_boundary.py asserts that no module under synapse_cdm/ imports hashlib or any crypto library, and this model does not change that: it is a container for a value produced outside the contract layer, exactly as Integrity is a container for a signature this package does not make. algorithm is therefore required and free-form — a digest with no algorithm cannot be reproduced by anyone, and an enum would make the day SHA-3 arrives a MAJOR bump for a string nobody branches on.

FieldTypeRequiredDescription
algorithmstringyese.g. sha256. Named by the producer. (min length 1)
valuestringyesThe digest, in the producer's own encoding. (min length 1)

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

SourceId​

One external identifier for an object — the provenance mapping.

A list of these, not one, because the same object arrives from several systems: the same vessel is an MMSI to AIS, a track number to STANAG 4676 and a UID to TAK. Fusion joins them later; the adapter's job is to record which name its own system used, and never to overwrite another system's entry.

FieldTypeRequiredDescription
external_idstringyesThat system's own identifier. (min length 1)
systemstringyesThe external system, e.g. PNTMAP, TAK. (min length 1)

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

SourceRef​

Which adapter produced this object, from which system, and whether it is real.

synthetic is required and has no default. Every fixture in this repository is synthetic and every scenario package is too (TR-12), and the platform keeps the synthetic and live layers apart over one interface — so an object that does not say which layer it belongs to cannot be filed. A default of false would silently promote exercise data to operational data, which is the dangerous direction; a default of true would silently demote live data and hide it from an operator. There is no safe default, so there is no default.

FieldTypeRequiredDescription
adapterstringyesAdapter name, e.g. pntmap. (min length 1)
adapter_versionstringyesAdapter semver: MAJOR.MINOR.PATCH, no leading zeroes, nothing else. (pattern ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$; min length 1)
format_namestring | nullnoThe source STANDARD's name, e.g. 'ASTERIX'. Filled from the adapter's own declared metadata.format; None only for an adapter that declares none. Default null.
format_versionstring | nullnoThe edition of that standard, e.g. 'Cat 062 ed 1.18'. None = no document in this tree states which edition the adapter targets — a reading, never a gap filled in. Default null.
observed_atstring | nullnoThe instant the SOURCE RECORD states for itself, when it states one and no canonical field already carries it. Event.observed_at stays the event's own time; this is the record's. Default null. (pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$)
original_idstring | nullnoThe source record's OWN identifier, as a first-class provenance field. Distinct from source_ids, which is the identity of the THING; this is the identity of the RECORD that described it. Default null.
record_indexinteger | nullnoWhich record of a multi-record payload this object came from, 0-based. None = the payload was not a sequence, or the adapter does not track it. Never -1 and never 0-as-unknown: 0 is the first record. Default null. (≥ 0)
source_hashSourceHash | nullnoDigest of the source record, computed by the adapter. Default null.
syntheticbooleanyestrue for anything not from a real source (TR-12).
systemstringyesThe external system this came from. (min length 1)
transformationsarray<string>noRule 5's transformation chain: the TRANSFORMS reasons this adapter applied, in the order it applied them. Empty = nothing was transformed, which is a claim the lossless check can contradict.

additionalProperties: false — unknown keys are rejected. Source-specific fields belong in the declared extension bags (Entity.attributes, Event.payload).

Verification​

How corroborated the assertion is. NOT confidence — 06-verification-and-confidence.md.

They vary independently and every combination is meaningful: a single high-grade sensor can be highly confident and entirely uncorroborated, and three weak sources can corroborate each other and leave the producer unsure. Absence means nothing is asserted about corroboration; UNVERIFIED is the positive claim that the question was asked and the answer was "no", which is a different and more informative fact. DISPUTED records that a disagreement exists and is not a verdict on it.

Closed vocabulary — a value outside this list is invalid, and UNKNOWN is a

member rather than a null wherever the enum has one.

Value
UNVERIFIED
CORROBORATED
VALIDATED
DISPUTED