Reference

The bus

Nine topic families. Five carry the core skill loop; four carry the distribution layer — media arrivals, asset-to-destination links, on-air state, and the governance trail.

Topic reference

Everything on the wire

Topic names carry the som. prefix. Status reflects the reference implementation — the contract is stable regardless, so integrate against the schema.

TopicMessage typesProducer → consumerStatus
som.story.context
Inbound story snapshots from the newsroom. Republished in full on every change.
story.context
NRCS / AP / test producer
Executor, dashboard
live
som.skills.staging
Skill outputs awaiting a human decision. The executor can publish nowhere else.
skill.warning.raised
skill.suggestion.created
Executor
Dashboard (pending lane)
live
som.skills.events
The production bus. Everything here has been approved by a person.
approved skill outputs
Approval gate
Downstream production systems
live
som.skills.rejected
Rejected outputs, kept for audit. A reject is a recorded non-action, not a deletion.
rejected skill outputs
Approval gate
Audit systems
live
som.skills.runs
Execution records: latency, what was read, what was emitted. One per skill per story version.
skill.run.completed
Executor
Dashboard (audit)
live
som.delivery.media_available
The availability handshake: essence has arrived in, or is growing inside, a store.
delivery.media_available
MAM / TAMS store
Media coordinator
mock producer
som.link.committed · .gate_changed · .withdrawn
Asset-to-destination commitments, with a compliance gate evaluated per destination.
link.committed
link.gate_changed
link.withdrawn
Distribution participants
Maintains assets[].usage[]
schema ready
som.telling.started · .ended · .exposed
On-air exposure events. On-air state is derived from these, never stored on the asset.
telling.started
telling.ended
telling.exposed
Playout / distribution
Derives on-air state
schema ready
som.system.audit
The governance trail: CLEARED for approvals, WITHHELD for rejections and safe-state stops.
system.audit
Coordinator, approval gate
Audit / dashboard
mock producer

Distribution layer

The TAMS junction

som.delivery.media_available announces that media has arrived in — or is growing inside — a store. That is all it does. SOM never queries the MAM and never moves bytes.

{
  "message_type": "delivery.media_available",
  "delivery_id":  "0190a000-0000-7000-8000-00000000aaa1",
  "asset_id":     "a2",
  "source":       "tams://tams-gcp-store/9f2e7c1a",
  "time_range":   "[0:0_134:0)",
  "arrived_in":   "tams-gcp-store",
  "arrived_at":   "2026-06-12T09:30:00Z"
}
  • source must be a fully-qualified TAMS Source URI, and it resolves to the same Source the asset references in assets[].media_refs[].source.
  • time_range is a TAMS timerange: bracketed seconds:nanoseconds bounds. Open-ended start or end is permitted — a feed still recording is addressable as [0:0_).
  • There is deliberately no story_id. You resolve asset_id → Asset → Story from story.context.
How a consumer reacts
Known asset, rolling rangeNoted. No story change — consumers take what exists so far.
Capture completeStory republished: the asset flips CAPTURING → CAPTURED and the range is bounded. Skills re-run against the new snapshot.
Matches no storySafe-state stop: a WITHHELD record on som.system.audit. A story is never created by default.

“Capture finished” isn’t a first-class delivery field, so the reference implementation carries it exactly the way partners are told to carry their own unratified concepts: extensions: { "com.ibc-poc.capture_complete": true }.

Cold consumers. A consumer that joins late and has no cached story has three options, in preference order: replay som.story.context from the earliest retained offset and keep the latest version per story; hold the arrival briefly, since stories republish in full on every change and the next version is rarely far away; or after a bounded wait, treat it as unmatched and record the non-action. There is no story query API — resolution is stream-first by design.

Not a carrier for a livestream URL. A public stream you want to transcribe is a live ingest source, not arrived TAMS media. Carry an ingest URL as a vendor extension instead — and remember the delivery event carries no credentials and no bytes. Retrieval is a store concern below the SOM boundary.

Distribution layer

Links and Tellings

som.link.*

Asset ↔ Destination

A link is the commitment between an asset and a destination. The story’s assets[].usage[] is maintained only from these events, under idempotent-upsert rules: committed-only, fail-closed.

compliance_gate_status is per destination. The same asset can be CLEARED on a digital link and BLOCKED on a broadcast link at the same time — which is exactly how compliance actually works, and why gate state does not live on the asset.

som.telling.*

On-air exposure

A telling is an exposure event: started, ended, exposed. On-air state is derived from the telling stream and never stored on the asset.

The reason is the same one that drives links: one asset can be on air in several places at once, so “is it on air?” is only answerable per destination, from events.

Governance

som.system.audit — the trail

One topic, two vocabularies of outcome. CLEARED records a positive decision; WITHHELD records a non-action — whether it came from a person rejecting a warning or from a machine declining to guess.

  • Human gate decisions. Approve → CLEARED. Reject → WITHHELD, with the reviewer stamped into payload.extensions.
  • Coordinator non-actions. A media arrival matching no story → WITHHELD. Nothing is created; nothing is silently dropped.

That symmetry is the point. An automated newsroom in which refusal is invisible is indistinguishable from one that is broken.

Integrating

What you implement, by role

You are…ConsumeProduceWatch out for
NRCS / story sourcestory.context on som.story.contextKeep story_id stable, bump sequence_number per change, fresh message_id/timestamp per publish, same correlation_id for the story’s lifecycle.
Skill vendor (external executor)som.story.contextskill.warning.raised on som.skills.staging, plus skill.run.completedThe approval gate reads staging regardless of which language produced the output. Never publish to the production topic yourself.
MAM / media storedelivery.media_availableSource URIs, not Flow ids. Emit repeatedly with a growing time_range while a recording is still being captured.
Media-hungry tool
transcription, ML
som.story.context + som.delivery.media_availableYour outputs, via staging or your own topicThe delivery event tells you when the essence is reachable and where. It does not hand you bytes.

Next: how a skill is written →