# CloudEvents Standard

Hermodr uses the [CloudEvents](https://cloudevents.io/) specification as its event model. Every event published through the framework is represented as a `CloudEvent` object from the [`CloudNative.CloudEvents`](https://github.com/cloudevents/sdk-csharp) SDK.

## Why CloudEvents?

* **Interoperability** — CloudEvents is a CNCF specification adopted by Azure, Google Cloud, AWS, and many other platforms. Events can be consumed by any system that understands the standard.
* **Schema-agnostic** — The envelope is fixed; the `data` payload can be any content type.
* **Tooling ecosystem** — Schema registries, event bridges, and observability tools often natively support CloudEvents.

## The CloudEvent Envelope

A `CloudEvent` consists of a set of **required** and **optional** attributes:

| Attribute         | Type              | Description                                                                |
| ----------------- | ----------------- | -------------------------------------------------------------------------- |
| `id`              | `string`          | Unique identifier for this specific event occurrence                       |
| `source`          | `Uri`             | Identifies the context in which the event happened (e.g. your service URL) |
| `specversion`     | `string`          | Always `"1.0"`                                                             |
| `type`            | `string`          | Describes the kind of event (e.g. `"order.placed"`)                        |
| `datacontenttype` | `string?`         | MIME type of the `data` payload (e.g. `"application/json"`)                |
| `dataschema`      | `Uri?`            | URI of the schema that the `data` conforms to                              |
| `subject`         | `string?`         | Additional context about the subject of the event                          |
| `time`            | `DateTimeOffset?` | Timestamp when the event occurred                                          |
| `data`            | `object?`         | The event payload                                                          |

## Event time semantics

In Hermodr, `CloudEvent.time` is the **occurrence timestamp** of the business fact, not the transport delivery time.

* Use `time` to answer **when the event happened** in domain terms.
* Use transport/outbox/dead-letter metadata (`NextRetryAt`, `NextReplayAt`, delivery headers) to answer **when delivery was attempted**.
* Keep these concepts separate to preserve an accurate event record and make replay/audit timelines reliable.

When the event has no `time`, `EventPublisher` fills it through `IEventSystemTime.UtcNow` during enrichment. This allows deterministic tests by replacing the clock via `UseSystemTime<TClock>()`.

## How Hermodr Populates the Envelope

When you call `publisher.PublishAsync(data)` with an annotated data object, the `IEventFactory` service reads the `[Event]` attribute and populates the envelope as follows:

| CloudEvent attribute | Source                                                                                        |
| -------------------- | --------------------------------------------------------------------------------------------- |
| `type`               | `[Event]` attribute's `EventType` property                                                    |
| `id`                 | `IEventIdGenerator` (default: new GUID)                                                       |
| `source`             | `EventPublisherOptions.Source`                                                                |
| `time`               | `IEventSystemTime.UtcNow`                                                                     |
| `dataschema`         | `[Event]` attribute's `DataSchema`, or `EventPublisherOptions.DataSchemaBaseUri` + event type |
| `datacontenttype`    | `[Event]` attribute's `ContentType`                                                           |
| `data`               | The annotated object itself                                                                   |

Any additional CloudEvent attributes declared via `[EventAttributes]` (or AMQP-specific attributes) are merged in.

### Required-attribute enforcement

After enrichment the publisher checks that the four required attributes (`id`, `source`, `type`, `specversion`) are non-null and non-empty. If any are missing, an `InvalidCloudEventException` is thrown **before** the event reaches any channel. This means:

* `type` must always be provided — it is never auto-filled.
* `source` must be set on the event itself *or* via `EventPublisherOptions.Source`.
* `id` is auto-generated by `IEventIdGenerator`, so it effectively never fails the check.
* `specversion` is always `"1.0"` in the CloudNative SDK.

> Full payload validation (checking the `data` field against its declared schema) is deferred to a later milestone. See [Schema Validation](/event-schema/validation.md) for the roadmap item.

## Working with CloudEvent Directly

You can bypass the annotation system and construct a `CloudEvent` manually when you need full control:

```csharp
using CloudNative.CloudEvents;

var systemTime = serviceProvider.GetRequiredService<IEventSystemTime>();

var @event = new CloudEvent
{
    Id      = Guid.NewGuid().ToString(),
    Type    = "com.acme.order.shipped",
    Source  = new Uri("https://orders.acme.com"),
    Time    = systemTime.UtcNow,
    DataContentType = "application/json",
    Data    = new { OrderId = 42, TrackingNumber = "1Z999AA1" }
};

await publisher.PublishEventAsync(@event);
```

## Further Reading

* [CloudEvents Specification v1.0](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md)
* [CloudNative.CloudEvents SDK for .NET](https://github.com/cloudevents/sdk-csharp)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hermodr.deveel.org/core-concepts/cloudevents.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
