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.
| Topic | Message types | Producer → consumer | Status |
|---|---|---|---|
som.story.contextInbound story snapshots from the newsroom. Republished in full on every change. | story.context | NRCS / AP / test producer → Executor, dashboard | live |
som.skills.stagingSkill outputs awaiting a human decision. The executor can publish nowhere else. | skill.warning.raisedskill.suggestion.created | Executor → Dashboard (pending lane) | live |
som.skills.eventsThe production bus. Everything here has been approved by a person. | approved skill outputs | Approval gate → Downstream production systems | live |
som.skills.rejectedRejected outputs, kept for audit. A reject is a recorded non-action, not a deletion. | rejected skill outputs | Approval gate → Audit systems | live |
som.skills.runsExecution records: latency, what was read, what was emitted. One per skill per story version. | skill.run.completed | Executor → Dashboard (audit) | live |
som.delivery.media_availableThe 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 · .withdrawnAsset-to-destination commitments, with a compliance gate evaluated per destination. | link.committedlink.gate_changedlink.withdrawn | Distribution participants → Maintains assets[].usage[] | schema ready |
som.telling.started · .ended · .exposedOn-air exposure events. On-air state is derived from these, never stored on the asset. | telling.startedtelling.endedtelling.exposed | Playout / distribution → Derives on-air state | schema ready |
som.system.auditThe 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"
}sourcemust be a fully-qualified TAMS Source URI, and it resolves to the same Source the asset references inassets[].media_refs[].source.time_rangeis a TAMS timerange: bracketedseconds:nanosecondsbounds. Open-ended start or end is permitted — a feed still recording is addressable as[0:0_).- There is deliberately no
story_id. You resolveasset_id → Asset → Storyfromstory.context.
| Known asset, rolling range | Noted. No story change — consumers take what exists so far. |
| Capture complete | Story republished: the asset flips CAPTURING → CAPTURED and the range is bounded. Skills re-run against the new snapshot. |
| Matches no story | Safe-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
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.
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 intopayload.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… | Consume | Produce | Watch out for |
|---|---|---|---|
| NRCS / story source | — | story.context on som.story.context | Keep 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.context | skill.warning.raised on som.skills.staging, plus skill.run.completed | The approval gate reads staging regardless of which language produced the output. Never publish to the production topic yourself. |
| MAM / media store | — | delivery.media_available | Source 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_available | Your outputs, via staging or your own topic | The delivery event tells you when the essence is reachable and where. It does not hand you bytes. |