Construir

Primeros pasos

Lee los documentos breves, ejecuta el corpus de conformidad, valida tu propio mensaje con la validación de formatos activada y publícalo. Después elige un nivel y habla con el grupo de trabajo.

Paso 2

Ejecuta el corpus de conformidad

El repositorio de la especificación incluye ejemplos prácticos que deben validar, veinte casos negativos que deben rechazarse y herramientas en Python puro que comprueban ambos.

git clone https://github.com/storyobjectmodel/som.git
cd som
pip install jsonschema rfc3339-validator

python3 tools/validate.py            # every example is legal
python3 tools/validate_negative.py   # every negative case is rejected
python3 tools/som_lint.py schema     # schemas agree with their own claims
python3 tools/validate_sequence.py examples/hurricane-run
                                     # a story over time holds together
Cuatro preguntas distintas
  • ¿Es válido este mensaje? — validate.py
  • ¿Restringen de verdad los schemas? — validate_negative.py. Diecinueve casos fallan en los schemas; el vigésimo, som_version: "0.3.2", falla por la regla de versión en el cable, que tu implementación debe aplicar por su cuenta.
  • ¿Coincide el schema con lo que dice de sí mismo? — som_lint.py
  • ¿Se sostiene una serie de snapshots? — validate_sequence.py, donde viven los errores caros.

Instala rfc3339-validator. Sin él, el jsonschema de Python acepta en silencio un timestamp mal formado. El corpus negativo incluye ese caso precisamente para que la comprobación falle de forma ruidosa cuando falte el paquete.

Paso 3

Valida en tu propio stack — con los formatos validados

Dos implementaciones que no se ponen de acuerdo en si message_id debe ser un UUID no son interoperables. En Node, eso significa la versión 2020-12 de Ajv más ajv-formats.

// npm i ajv ajv-formats
import Ajv2020 from 'ajv/dist/2020';
import addFormats from 'ajv-formats';

const BASE = 'https://storyobjectmodel.com/schema/1.0';
const FAMILY_SCHEMA: Record<string, string> = {
  'story.context': 'story-context',
  'skill.warning.raised': 'skill-warning',
  'delivery.media_available': 'delivery-media-available',
  'link.committed': 'link-event', 'link.gate_changed': 'link-event', 'link.withdrawn': 'link-event',
  'telling.started': 'telling-event', 'telling.ended': 'telling-event', 'telling.exposed': 'telling-event',
  'system.audit': 'system-audit',
};

const ajv = new Ajv2020({ strict: false, allErrors: true });
addFormats(ajv); // conformance §4: uuid and date-time MUST be asserted

const load = async (name: string) => (await fetch(`${BASE}/${name}.schema.json`)).json();

export async function validateMessage(msg: { som_version?: string; message_type?: string; payload?: unknown }) {
  const envelope = ajv.compile(await load('envelope'));
  if (!envelope(msg)) return { ok: false, errors: envelope.errors };

  // §3: 0.3.2 is not SOM 1.0 — and never branch on the version otherwise.
  if (msg.som_version?.startsWith('0.')) return { ok: false, errors: ['pre-1.0 som_version'] };

  // §2 and §5: dispatch on message_type only; ignore families you don't handle.
  const family = FAMILY_SCHEMA[msg.message_type ?? ''];
  if (!family) return { ok: true, ignored: true };

  const payload = ajv.compile(await load(family));
  return payload(msg.payload) ? { ok: true } : { ok: false, errors: payload.errors };
}

Ilustrativo, no una implementación de referencia — guarda en caché los validadores compilados en lugar de descargar los schemas en cada mensaje. Los schemas se sirven en la URL de su propio $id, así que resolverlos por identificador también funciona.

Paso 4

Publica un snapshot de Story conforme

{
  "som_version": "1.0.0",
  "message_id": "0199a1c4-7a2e-7b31-8c55-4d2f9e6a1b07",
  "correlation_id": "0199a1c4-0000-7000-8000-000000000911",
  "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",
  "payload": {
    "story_id": "hurricane-2026-0911",
    "slug": "HURRICANE-LANDFALL",
    "headline": "Hurricane makes Cat 4 landfall",
    "story_type": "ACTIVE",
    "lifecycle": { "phase": "BREAKING",
                   "phase_entered_at": "2026-09-11T14:00:00Z" },
    "sequence_number": 7,
    "updated_at": "2026-09-11T16:41:00Z"
  }
}
Lista de comprobación para cada snapshot
  • Un message_id UUID nuevo (se recomienda v7); el mismo correlation_id durante todo el ciclo de vida de la Story.
  • El mismo story_id, un sequence_number mayor, un updated_at posterior.
  • Todos los campos, incluidos los que aportaron otros sistemas. Omitir significa ausente.
  • lifecycle presente si y solo si story_type es ACTIVE.
  • Tu propio originating_system, vuelto a sellar en cada publicación.
  • Todo lo que el estándar no lleva va en extensions["com.{vendor}.…"].

Míralo en un bus en vivo. El emulador ↗ del proyecto es un panel sobre un bus en funcionamiento que puedes usar tú mismo, y las demos del IBC ↗ muestran dos escenarios completos de principio a fin. Una implementación de referencia en .NET llegará en un repositorio aparte; el kit de inicio del hackathon, anterior a 1.0, emite 0.3.2 y no es tráfico 1.0.

Unirse

No tienes que sacar nada de tu producto

Basta un sistema para empezar. Las integraciones se describen en tres niveles.

1

Suscribirse y publicar

Lee la Story y pon tu propia familia en el bus. El proyecto lo calcula en días, no en meses.

2

Contexto bidireccional

Tu herramienta consume y también aporta contexto estructurado — assertions, procedencia, links, Tellings.

3

Decidir según la Story

Tu herramienta actúa de forma autónoma sobre lo que lee, mediante un executor que aplica las skills de la casa.