1019. Lesson identity
This lesson defines the promise made between a sender and a receiver when a system communicates through a message.
1020. Learning objective
After this lesson, you can write and evaluate one complete event contract that identifies the sender, receiver, payload, timing, occurrence identity, delivery guarantee, and failure behavior.
1021. Why this matters
A message creates a dependency between systems. If the receiver interprets the payload differently from the sender, a system can produce duplicated effects, stale presentation, or unexplained state changes. A written contract makes that dependency inspectable before implementation and gives an AI coding partner precise boundaries instead of leaving the event's meaning implicit.
1022. Prior knowledge
You should be able to:
- distinguish authoritative writers from readers using the state-ownership sheet from 3.2 L2 — Build a state-ownership sheet;
- identify which system owns a state transition;
- describe a state change without confusing the change with its presentation.
You do not need to know a particular messaging library. This lesson concerns the agreement, not a specific transport mechanism.
1023. Core concept
An event contract is the smallest explicit agreement that lets one system communicate a meaningful occurrence to another system.
Use the S-R-P-T-F contract:
- Sender: Which system emits the message, and after which authoritative decision?
- Receiver: Which system or systems may react, and what are they allowed to do?
- Payload: Which fields are included, what does each field mean, and which field identifies this particular occurrence?
- Timing: When is the message emitted, can delivery be delayed or out of order, and what delivery guarantee applies?
- Failure behavior: How are duplicates and invalid data handled, and what sender, transport, or reconciliation behavior applies if delivery does not occur?
A contract is complete only when another developer can predict the permitted behavior without reading the sender's implementation.
1024. Terms in plain language
- A stable identifier is a value that continues to refer to the same entity or occurrence wherever the message is processed.
- An occurrence identifier, such as
eventIdortransitionId, identifies one specific thing that happened. It is not merely the identifier of the entity involved. - A session timestamp records when something happened according to the current play session's clock. It does not by itself prove delivery order.
- Delayed delivery means a message arrives later than expected.
- Out-of-order delivery means messages arrive in a different order from the order in which their events occurred.
- Idempotent handling means processing the same occurrence more than once has the same protected result as processing it once. The receiver recognizes a redelivery through an occurrence-level key.
- A delivery guarantee states what the sender or transport promises, such as durable retry, retry until timeout, or best-effort delivery with later state reconciliation.
Entity identity and occurrence identity are different. A gate may open, close, and open again. Both open transitions concern the same gateId, but they are separate occurrences and require different transition identifiers.
1025. Fact, reaction, and ownership
Keep these ideas separate:
- Fact: what the sender says has happened.
- Reaction: what a receiver may do with that fact.
- Ownership: which system remains responsible for the underlying state.
An event should report a committed fact, such as ContractAccepted or ItemDelivered. It should not quietly become a second owner of the state. The authoritative writer validates and commits the state change. A receiver may update a view, schedule another action, or record evidence only as permitted by the contract.
1026. Concrete example
Suppose GateSystem owns whether a gate is open. After a valid interaction, it commits a closed-to-open transition and emits GateOpened.
An incomplete description is:
GateSystem sends a gate-opened event to ObjectiveSystem.
A usable contract is:
Event: GateOpened
Sender:
GateSystem, after validating the interaction and committing one
closed-to-open transition.
Receiver:
ObjectiveSystem may update the matching objective instance.
It may not change the gate's authoritative state.
Payload:
transitionId: stable identifier for this particular open transition;
required string.
gateId: stable identifier for the gate entity; required string.
openedBy: stable identifier for the responsible actor; required string.
openedAt: session timestamp of the committed transition; required number.
Timing:
Emit after the authoritative commit. Delivery may be delayed or out of order.
The transport retries until acknowledged or until its stated timeout.
Failure behavior:
ObjectiveSystem deduplicates by transitionId within the current objective
instance. A redelivery of the same transitionId cannot award progress twice.
A later valid opening of the same gate has a new transitionId and is not
discarded merely because gateId was seen before.
Unknown gates or missing required fields are recorded and cause no progress.
If retry expires, the transport reports the failure and ObjectiveSystem
reconciles against authoritative objective and gate state.
Deduplicating by gateId would be incorrect because it could suppress a later valid opening. Idempotency protects the effect of one occurrence; it does not forbid every future event concerning the same entity.
1027. Delivery failure belongs to more than the receiver
A receiver can reject malformed data or recognize a duplicate only after receiving a message. It cannot react to a message that never arrives.
A non-delivery decision must therefore name the responsible layer and its guarantee. Defensible strategies include:
- the sender retries until an acknowledgement or timeout;
- a durable queue retains the message until it can be delivered;
- the system records a timeout and exposes it through logs or monitoring;
- the receiver later reconciles its view with authoritative state;
- the contract explicitly declares best-effort delivery when losing the update is acceptable.
Choose a strategy that matches the consequence of loss. Do not write only “the receiver handles non-delivery.”
1028. Guided practice
Write a contract for this scenario:
InventorySystemaccepts a pickup, commits the inventory change, and informsHUDSystem. The HUD should refresh, but it must not add the item itself.
Your contract must state:
- the sender and the decision that occurs before emission;
- the receiver's permitted reaction;
- every required payload field and its meaning;
- an occurrence-level identifier for this pickup event;
- when emission occurs and whether delivery may be late or out of order;
- the deduplication scope;
- handling for malformed messages;
- the sender, transport, or reconciliation strategy for non-delivery.
Do not deduplicate only by itemId: the same item entity or item type may participate in more than one legitimate inventory occurrence.
1029. Reference contract and comparison
Compare your draft with this compact reference:
Event: InventoryChanged
Sender:
InventorySystem, after committing the accepted pickup.
Receiver:
HUDSystem may refresh the inventory presentation. It cannot mutate inventory.
Payload:
pickupEventId: identifier for this committed pickup occurrence; required.
itemId: identifier for the affected item or item definition; required.
inventoryVersion: authoritative inventory version after the commit; required.
quantityAfter: displayed quantity after the commit; required.
Timing:
Emit after commit. Delivery may be delayed or out of order.
Retry until acknowledgement or timeout.
Failure behavior:
HUDSystem deduplicates pickupEventId within the gameplay session.
It does not deduplicate by itemId.
It ignores an older inventoryVersion rather than reverting the display.
It rejects malformed payloads and records the reason.
After retry timeout, it requests or receives an authoritative inventory
snapshot and refreshes from that state.
The reference is not the only defensible payload. A contract may instead send:
- a complete inventory snapshot, which is larger but straightforward for the HUD to render; or
- a compact change plus
inventoryVersion, which is smaller but requires ordering and reconciliation rules.
Either strategy is acceptable when the contract makes its tradeoffs and failure behavior explicit.
1030. Practical assessment
Complete Repair an event contract with S-R-P-T-F. You will repair an incomplete inventory event contract by adding occurrence identity, timing and delivery guarantees, ownership boundaries, and failure decisions. The assessment is scored against the five S-R-P-T-F parts.
1031. Validation checklist
Before submitting a contract, verify each item without relying on the table format:
- Sender: Names the authoritative system and says emission occurs after commit.
- Receiver: Names the permitted reaction and prohibits unauthorized state mutation.
- Payload: Defines required fields and includes an occurrence-level identifier.
- Timing: States emission time, possible delay or reordering, and a delivery guarantee.
- Failure: Defines duplicate scope, invalid-data behavior, and non-delivery responsibility.
1032. Key takeaways
- A message is a contract, not merely a label or function call.
- A complete contract specifies sender, receiver, payload, timing, and failure behavior.
- Entity identifiers and occurrence identifiers serve different purposes.
- Idempotency prevents one occurrence from applying its protected effect twice; it must not suppress later valid occurrences for the same entity.
- Receiver behavior covers messages that arrive. Sender, transport, timeout, or reconciliation rules cover messages that do not arrive.
- State ownership remains with the authoritative writer.
1033. Next lesson
Continue to 3.3 L2 — Avoid event soup / Evitar el event soup.
1034. Knowledge check
Answer these items for yourself before reading the answers.
Which set contains all five parts of the S-R-P-T-F event contract?
Show answer and feedback
Answer: Sender, receiver, payload, timing, and failure behavior
Why: S-R-P-T-F covers who communicates, who may react, what data is communicated, when and under which delivery assumptions it is communicated, and how failures are handled.
A gate can open, close, and open again. Which field should a receiver use to recognize a redelivery of one particular GateOpened occurrence?
Show answer and feedback
Answer: A transitionId unique to the committed opening occurrence
Why: gateId identifies the entity, not one opening occurrence. A unique transitionId lets the receiver suppress a redelivery without discarding a later valid opening of the same gate.
Which contract statement correctly addresses a message that never reaches its receiver?
Show answer and feedback
Answer: The transport retries until timeout, reports failure, and the view later reconciles with authoritative state.
Why: A receiver cannot handle a message it never receives. Non-delivery behavior must assign responsibility to the sender, transport, timeout monitoring, durable storage, or later state reconciliation.