Reference · SOM 1.0

The envelope

One wrapper, shared by every message in every family. It is a closed object: unknown top-level fields fail validation, and the legacy source and signature fields are rejected outright.

Anatomy

Every field, one at a time

Click a key to read what it carries and where it trips people up. Sample values follow beat 6 of the specification’s worked hurricane run.

{
"som_version": "1.0.0",
"message_id": "0199a1c4-7a2e-7b31-8c55-4d2f9e6a1b07",
"correlation_id": "0199a1c4-0000-7000-8000-000000000911",
"causation_id": "0199a1c4-7a2e-7b31-8c55-4d2f9e6a1b06",
"message_type": "story.context",
"timestamp": "2026-09-11T16:41:00.000000Z",
"originating_system": { "system_id": "ncs-nyc-01", "system_type": "ncs", … },
"topic": "som.story.context.hurricane-2026-0911",
"modification_header": { "story_version": 7, "modified_by": "producer-7" },
"_actors": { … },
"@context": null,
"extensions": { "com.example.desk": "news" },
"payload": { "story_id": "hurricane-2026-0911", … }
}

som_version

requiredstring (semver)

The schema pack version the payload conforms to — "1.0.0" for SOM 1.0.

Informative only: consumers MUST NOT branch on it. "0.3.2" is not SOM 1.0 — three fields were withdrawn before ratification — so a consumer must not treat it as conformant.

Schema: https://storyobjectmodel.com/schema/1.0/envelope.schema.json ↗

Conformance

The floor is lower than you think

A system is SOM 1.0 conformant if every message it publishes is a valid envelope, every payload validates against the schema for its declared message_type, and it ignores what it doesn’t recognise. A planning system that only publishes story.context and only reads warnings is fully conformant.

01

Emit "1.0.0", never branch on it

Producers emit the pack version they conform to; a future 1.1 producer emits "1.1.0". Consumers must not switch behaviour on som_version — that breaks at 1.1 by definition. And "0.3.2" is not SOM 1.0: the schema leaves the field open, so a conformant consumer has to apply this rule itself.

02

message_type is the only discriminator

Select the payload schema from it. Not from the topic, a filename, the publisher’s identity or the shape of the payload.

03

Assert format

JSON Schema treats format as an annotation, so most validators accept "message_id": "NOT-A-UUID" with zero errors. Conformance requires assertion for uuid and date-time — in Node, ajv/dist/2020 plus ajv-formats; in Python, a format_checker and rfc3339-validator.

04

Ignore what you don’t recognise

Unknown extensions keys are ignored, never grounds for rejection. A message_type you don’t handle is ignored too — which is what lets 1.x add families without breaking anyone.

05

correlation_id and topic are required

correlation_id links every message about one story lifecycle. topic must begin som. — beyond that, topic layout is yours.

+

Say what you produce and consume

Implementations should state which families they publish and which they read. That statement, not the size of the implementation, is what an integrator needs. spec/conformance.md ↗

Payload

skill.warning.raised

What a skill declared about a story, put on the bus by the executor in the tool that recalled it. Twelve required fields, a closed object, and two fields explicitly forbidden.

{
"warning_id": "0190a000-0000-7000-8000-00000000000a",
"skill_id": "smart-stories/hold-while-flagged",
"skill_version": "0.2.2",
"story_id": "story-verdict-0412",
"scope": "story:story-verdict-0412",
"severity": "hold",
"rule_id": "house-reporting-restriction-hold",
"non_overridable": true,
"affected_fields": ["editorial_gates[].status"],
"detail": "Held off air. … Clears on a clearance at story scope by legal or above.",
"blocks": ["air:story-verdict-0412"],
"skill_warning_ref": "swr-0001",
"content_type": "sync.ai.banner",
"message_type": "skill.warning.raised"
}

warning_id

requiredstring (UUIDv7)

Identity of this warning.

Scope is the firing level

link:{link_id} for a destination-specific skill, asset:{asset_id} for one asset, story:{story_id} for the whole story.

A clearance only counts at the scope the hold was declared at: a link clearance never lifts a story hold.

Timestamps live on the envelope

The warning payload rejects both timestamp and the retired instance_ref. When the warning was raised is the envelope’s timestamp; what caused it is the envelope’s causation_id.

Migration

Coming from v0.3.2

1.0 is a clean break, and a small one: stop emitting three withdrawn fields, emit som_version: "1.0.0", and turn on format assertion. Every other field, type, enum and constraint is unchanged.

Withdrawn at 1.0What it was
assets[].voice_countAn optional integer carrying an editorial classification rule that was never settled.
assets[].status → FINALIZINGAn enum member proposed for a derived output still finishing; never ratified.
transforms[].transform_idAn optional stable audit handle for a single transform; still under discussion.
Identifiers moved

Every schema $id is now a resolvable URL that serves the file, such as https://storyobjectmodel.com/schema/1.0/story-context.schema.json, stable for the life of 1.x. The old som.spec/schema/v0.3… identifiers were never resolvable.

Layout is flat

The three version directories are gone: one file per family, no version in the filename. Anything that dispatched on "v0.3.2" in a schema path must select on the family instead. authenticity_credential, proposed in v0.3.2, ships in 1.0 as normative.

spec/migration-from-v0.3.2.md ↗ · spec/compatibility-policy.md ↗

Renamed & retired

Old paths that no longer validate

If a producer or configuration predates the v0.3 envelope lock, these have moved — or are rejected by the 1.0 schemas.

OldNow
source (envelope)originating_system — the old field is hard-rejected
signature (envelope)Hard-rejected. There is no signature field.
sources[]editorial_source[] — credibility TRUSTED | VERIFIED | ENDORSED | UNVERIFIED
skills_config.broadcasterskills_config.newsroom (the old key is rejected)
broadcaster_idHard-rejected. newsroom_id is optional at 1.0, derived from skills_config.newsroom if absent
collaboration.versioncollaboration.editing_version
content_ref (singular)content_refs[]
instances[]Hard-rejected. Distribution is modelled by links and tellings
instance_ref (warning)Hard-rejected. Outputs scope through scope: link:, asset: or story:
ai_enrichments[]Hard-rejected since v0.3.2. Generative output that publishes is an assets[] entry with provenance; claims about content are assertions[]
media_refs[].flow_idsource (tams://store/id) + time_range, or a locator
editorial_gates[].blocks as stringsDeprecated. Use { "kind": "ASSET" | "PHASE", "ref": … }

Extensions

The defined place for what the standard doesn’t carry

Envelope, story and every event family accept an extensions object whose keys must match com.{vendor}. — anything else fails validation. Consumers ignore keys they don’t recognise.

{
  "warning_id":  "0190a000-0000-7000-8000-00000000000a",
  "skill_id":    "nbcu/editorial-standards",
  "scope":       "link:l1",
  "severity":    "flag",
  "detail":      "Informal term 'cops' in headline.",
  "extensions": {
    "com.nbcu.rationale": "Style guide proscribes informal register in headlines."
  }
}
  • Vendor enum values use a different convention: x- plus a lowercase token — x-graphics_pack. It applies to the governed-but-extensible registries such as asset_type, source_type and transform_type.
  • Tags have their own vendor scheme: com.{vendor}.{name} alongside newsroom and iptc-mediatopic.

Next: the seven families these messages belong to →