Appearance
How an executor behaves
What a skill executor does on a SOM bus: what it reads, what it publishes and when, and how it behaves on redelivery, late snapshots and errors. It's language-neutral. The contract is a draft for review, for the suite som-1.0.0+lib-0.2.2.
Every rule names its source: SOM, the skill library, an RND position (P-nn, see RND positions: ours, not the standard) or the bus. "Must", "should" and "may" are used as in RFC 2119. A "must" that rests on a position binds RND's reference executor and test tools, and nothing else.
1. Identity
| Rule | Source |
|---|---|
The executor publishes as a connected producer app. Its system_id must be one the connection may stamp | The bus: producer.system_id_not_allowed |
The connection must be granted skill.warning.raised (or skill.*) | The bus: producer.message_type_not_allowed |
system_type must be a SOM 1.0 value. Which one: the value closest to the tool the executor sits beside | SOM; P-02 |
2. What it subscribes to
| Rule | Source |
|---|---|
For each configured instance, the skill's recall_on list. In a house, the configured instances are the ones it registered for you | Skill library |
Always story.context, even if no recall_on names it: nothing can be evaluated without the latest snapshot | P-06 |
recall_on entries match the envelope's message_type, never topic | SOM; P-03 |
Message types it doesn't handle, and extensions it doesn't recognise, are ignored without failing | SOM |
On the bus, a subscription is a consumer connection filtered by message_type: see Subscriptions and filters. Only 3 of the 15 topics the library names have a SOM 1.0 schema; see Topics without a 1.0 schema.
3. Evaluation
| Rule | Source |
|---|---|
| An evaluation reads the whole snapshot | SOM |
| A trigger that isn't a snapshot is a wake-up: evaluate against the latest snapshot held for that story. No snapshot held, no evaluation | P-06 |
| The outcome depends only on the snapshot, the configured instance and the skill version: no clock, no randomness, no call-outs that change the answer | P-06 |
Never change content: no story.context, no asset edits, no config writes | Skill library |
Never publish story.context because of a warning, not even to turn a flag into a gate | P-13 |
| Absent is a quiet non-match. Present but unreadable, or config missing, fails closed at the loudest severity allowed | Skill library |
| A configured instance never triggers on a warning it raised itself | P-14 |
| Most evaluations raise nothing, and that's normal | Skill library |
4. A declaration's life
| Event | The executor publishes | Source |
|---|---|---|
| The condition starts to hold | One warning: a new warning_id, a new skill_warning_ref | Skill library; P-08, P-09 |
| A later snapshot, nothing changed | Nothing | P-08 |
A later snapshot, the warning would now differ (severity, blocks, affected_fields, the values in detail) | One new warning: new warning_id, same skill_warning_ref | P-08 |
| The condition stops holding, or a valid clearance is recorded | Nothing on the bus. The closure is kept in the executor's own state and log | P-11 |
| The same condition holds again after closing | A new declaration: new warning_id, the skill_warning_ref counter goes up | P-09 |
The story becomes KILLED, SPIKED or ARCHIVED | Nothing. Open declarations close; no new ones open | P-12 |
A warning is never "retracted" by publishing a correction: SOM 1.0 has no message for it.
5. Delivery
Idempotency
| Rule | Source |
|---|---|
| A trigger delivered twice produces the warnings of one delivery, not two | Skill library |
Key each emission on (configured instance, trigger message_id). Store the outgoing message before publishing it, and on a retry send the stored bytes | P-10 |
The bus answers the same message_id and content with 200 duplicate, and any other content with 409 | The bus: Retries and idempotency |
| Keep the emission record for at least the bus's 7-day idempotency window | P-10 |
Order and late snapshots
| Rule | Source |
|---|---|
| Don't rely on order across stories | SOM |
The bus delivers in order per correlation_id, and refuses a snapshot whose sequence_number doesn't increase | The bus |
Still, evaluate a snapshot only if its sequence_number is higher than the highest already evaluated for that story. Replays and redrives can deliver old ones | P-07 |
Acknowledging a trigger
Acknowledge a trigger (or delete it from your queue) only after every warning it caused has been accepted (202) or answered as a duplicate (200). A crash between evaluation and publish then ends in a redelivery, not a lost warning.
6. Errors
| Situation | The executor |
|---|---|
400 from the bus | Has a defect. Doesn't retry; logs the rule ids; acknowledges the trigger so it doesn't loop |
401 or 403 | Has a configuration or credential problem. Doesn't retry the message; alerts |
409 message_id.reused | Has a defect: the retry wasn't byte-identical. Doesn't retry |
413 | Shortens detail. Media never travels the bus |
503, a timeout, a network failure | Retries the stored bytes with backoff; doesn't acknowledge the trigger until it succeeds |
| A trigger that won't parse | Logs it and acknowledges it. There's nothing to evaluate |
7. Timing
| Rule | Source |
|---|---|
| An executor should publish within 5 seconds of receiving its trigger | P-15 |
| The skill harness in test runs fails a case with no warning after 30 seconds, and passes a no-trigger case if nothing arrives in that time | P-15 |
8. Versions
| Rule | Source |
|---|---|
A configured instance is registered against one skill file at one skill_version, and its warnings carry that version. See Registrations | Skill library; P-04 |
Never emit a skill_version you don't implement | This contract |
A new library release is a new suite (som-1.0.0+lib-0.2.3), never an edit of this one | This contract |
A 1.x som_version on a trigger changes nothing | SOM |
9. Registrations
Available now In a house, the house decides which configured instances yourexecutor runs, and with which values. It registers each one (skill, version, instance_label, values and its authority scale) and binds it to your executor's connection. See Run skills in your house.
| Rule | Source |
|---|---|
Read your registrations with your consumer connection's own credential: GET /v1/tenants/{t}/consumers/{c}/skills, where {c} is your client id. You get only the configured instances bound to that connection | The bus |
Values always come from the registration, never from the story. instance_label is the warning's rule_id | Skill library; P-04 |
A story with no skills_config gets every configured instance you read. A story with one gets only those whose skill_id is in its active_skills (an empty list: none). A listed skill_version that differs from the registered one skips that instance on that story, and you log why | P-04 |
Compare authorities on the registration's authority_scale, most senior first. An authority not on it ranks below the declaring one | P-16 |
Read again at startup and at least every 5 minutes. A higher revision means the house changed the values or the binding; a configured instance that's gone is removed or bound elsewhere, and you stop evaluating it | The bus |
If the read fails (503 skills.unavailable), keep running what you read last and retry with backoff | The bus |
The answer:
json
{
"suite": "som-1.0.0+lib-0.2.2",
"connection_id": "c…",
"narrowing": "P-04",
"registrations": [
{
"registration_id": "k…",
"skill_id": "smart-stories/raise-flag-on-match",
"skill_version": "0.2.2",
"instance_label": "house-breaking-indicative-category",
"values": { "match_field": "lifecycle.phase", "match_value": "BREAKING", "flag_name": "BREAKING", "clearing_authority": "duty-editor" },
"authority_scale": ["editor-in-chief", "duty-editor", "producer"],
"connection_id": "c…",
"revision": 1,
"updated_at": "2026-09-27T12:00:00.000Z"
}
]
}The bus checks every registration against the skill's config surface when the house registers it: unknown fields are refused, and every configured field path that reads the story (match_field, watched_field, declared_fields and the like) must be a field of the SOM 1.0 story.context schema.
Test it
The skill harness in Test runs triggers your executor with fixtures in your workspace and checks every warning against this contract. You can also test a skill by hand. A test run plays in your own vendor workspace, which is not a house, so there your executor loads the harness's configured instance itself.