Bauen

Erste Schritte

Lesen Sie die kurzen Dokumente, lassen Sie das Konformitätskorpus laufen, validieren Sie Ihre eigene Nachricht mit eingeschalteter Format-Assertion und veröffentlichen Sie sie. Dann wählen Sie eine Stufe und sprechen mit der Arbeitsgruppe.

Schritt 2

Das Konformitätskorpus ausführen

Das Repository der Spezifikation liefert durchgerechnete Beispiele, die validieren müssen, zwanzig Negativfälle, die abgewiesen werden müssen, und reine Python-Tools, die beides prüfen.

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
Vier verschiedene Fragen
  • Ist diese Nachricht gültig? — validate.py
  • Schränken die Schemas tatsächlich ein? — validate_negative.py. Neunzehn Fälle scheitern an den Schemas; der zwanzigste, som_version: "0.3.2", scheitert an der Regel zur Wire-Version, die Ihre Implementierung selbst anwenden muss.
  • Stimmt das Schema mit dem überein, was es über sich selbst sagt? — som_lint.py
  • Hält eine Folge von Snapshots zusammen? — validate_sequence.py, dort, wo die teuren Bugs wohnen.

Installieren Sie rfc3339-validator. Ohne ihn akzeptiert Pythons jsonschema stillschweigend einen fehlerhaften timestamp. Das Negativkorpus enthält genau diesen Fall, damit die Prüfung laut scheitert, wenn das Paket fehlt.

Schritt 3

Im eigenen Stack validieren — mit durchgesetzten Formaten

Zwei Implementierungen, die sich uneinig sind, ob message_id eine UUID sein muss, sind nicht interoperabel. In Node heißt das: der 2020-12-Build von Ajv plus 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 };
}

Zur Veranschaulichung, keine Referenzimplementierung — cachen Sie kompilierte Validatoren, statt pro Nachricht nachzuladen. Die Schemas werden unter der URL in ihrer eigenen $id ausgeliefert, die Auflösung per Identifier funktioniert also ebenfalls.

Schritt 4

Einen konformen Story-Snapshot veröffentlichen

{
  "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"
  }
}
Checkliste für jeden Snapshot
  • Eine neue UUID als message_id (v7 empfohlen); dieselbe correlation_id für den gesamten Lebenszyklus der Story.
  • Dieselbe story_id, eine höhere sequence_number, ein späteres updated_at.
  • Jedes Feld, auch die, die andere Systeme beigesteuert haben. Weglassen heißt: nicht vorhanden.
  • lifecycle genau dann vorhanden, wenn story_type gleich ACTIVE ist.
  • Ihr eigenes originating_system, bei jeder Veröffentlichung neu gestempelt.
  • Alles, was der Standard nicht trägt, gehört unter extensions["com.{vendor}.…"].

Sehen Sie es auf einem laufenden Bus. Der Emulator ↗ des Projekts ist ein Dashboard auf einem laufenden Bus, das Sie selbst nutzen können, und die IBC-Demos ↗ zeigen zwei vollständige Szenarien von Anfang bis Ende. Eine .NET-Referenzimplementierung folgt in einem separaten Repository; das Hackathon-Starterkit aus der Zeit vor 1.0 sendet 0.3.2 und ist kein 1.0-Traffic.

Einstieg

Nichts über Ihr Produkt muss offengelegt werden

Ein System reicht für den Anfang. Integrationen werden in drei Stufen beschrieben.

1

Abonnieren und veröffentlichen

Lesen Sie die Story und legen Sie Ihre eigene Familie auf den Bus. Das Projekt schätzt den Aufwand auf Tage, nicht Monate.

2

Kontext in beide Richtungen

Ihr Tool konsumiert strukturierten Kontext und steuert ihn auch bei — Assertions, Herkunft, Links, Tellings.

3

Anhand der Story entscheiden

Ihr Tool handelt eigenständig auf Grundlage dessen, was es liest — über einen Executor, der die Skills des Hauses anwendet.