---
title: "AEP-0005: Bridge Annexes (CloudEvents, OTLP, and OCSF)"
sidebarTitle: "AEP-0005: Bridges"
description: "Defines the lossless, mechanical, reversible mapping between AEP Events and CloudEvents 1.0 (JSON format, structured mode), the export of AEP Events to OpenTelemetry as OTLP LogRecords, and the projection of AEP's security-relevant event families onto OCSF (Open Cybersecurity Schema Framework) class"
---

{/* GENERATED by docs/render-site.js from spec/AEP-0005-bridges.md; do not edit. Edit the source and re-run `node docs/render-site.js` */}

<Note>

**Draft**: the AEP suite is pre-`v0.1`; each document's status lives in its header table. This page is rendered from the canonical [`spec/AEP-0005-bridges.md`](https://github.com/agenteventprotocol/agent-event-protocol/blob/main/spec/AEP-0005-bridges.md), which is the normative text.

</Note>

| Field | Value |
|---|---|
| **AEP** | 0005 |
| **Title** | Bridge Annexes: CloudEvents Mapping, OTLP Export, and OCSF Projection |
| **Type** | Standards Track — Annex (normative) |
| **Status** | Draft |
| **Sponsor** | AEP maintainers |
| **Created** | 2026-07-03 |
| **Requires** | AEP-0001, AEP-0002 |
| **Supersedes / Superseded-by** | n/a |

> Provenance (non-normative): the
> bridges are **normative, day-one deliverables**: the mapping tables are
> specification text, the reference implementations are conformance-tested, and both
> are versioned and released with the specification; this is the standing answer to
> "adopters would rather ingest CE/OTLP".

## Abstract

Defines the lossless, mechanical, reversible mapping between AEP Events and
CloudEvents 1.0 (JSON format, structured mode), the export of AEP Events to
OpenTelemetry as OTLP LogRecords, and the projection of AEP's security-relevant
event families onto OCSF (Open Cybersecurity Schema Framework) classes for
security-analytics consumers.

Losslessness is a design property (the AEP envelope is deliberately
CE-isomorphic), so the CE mapping is five rules and one table, and round-trip
identity is a conformance requirement, not an aspiration. The OCSF projection
is deliberately partial by scope, not by loss: it carries the security
*signal* (sessions, tool activity, human-attention outcomes), and every record
embeds the complete source envelope.

## 1. CloudEvents mapping: forward (AEP → CE)

Output: a valid CloudEvents 1.0 event, JSON format, structured mode.

**Rules:**

- **B1 (constants).** Set `specversion: "1.0"`; set
  `datacontenttype: "application/json"` when `data` is present.
- **B2 (pass-through).** `id`, `source`, `subject`, `time`, `data` copy verbatim.
- **B3 (type).** `type := "dev.aep." + type` (reverse-DNS root; placeholder frozen
  at the naming decision). Vendor types map under the same root:
  `x.acme.foo.bar → dev.aep.x.acme.foo.bar`.
- **B4 (attributes).** Each remaining envelope attribute maps to its prefixed CE
  extension attribute per the table below. AEP envelope values are CE-legal
  extension scalars (string/integer/boolean) by design; there is no encoding step.
  AEP extension attributes (AEP-0001 §5.4) pass through verbatim (their naming rule
  is CE's).
- **B5 (trace context).** `traceparent` maps to the CE **Distributed Tracing
  extension** attribute `traceparent`, unprefixed (CE already standardizes it).

| AEP attribute | CE attribute |
|---|---|
| `aep` | `aepversion` |
| `agent` | `aepagentid` |
| `session` | `aepsessionid` |
| `run` | `aeprunid` |
| `step` | `aepstep` |
| `seq` | `aepsequence` |
| `epoch` | `aepepoch` |
| `cause` | `aepcausationid` |
| `severity` | `aepseverity` |
| `capture` | `aepcapture` |
| `traceparent` | `traceparent` |

Absent OPTIONAL AEP attributes produce no CE attribute (defaults are not
materialized).

## 2. CloudEvents mapping: inverse (CE → AEP), and scope

1. For CE events whose `type` begins with the frozen root: drop the B1 constants,
   strip the B3 prefix, reverse the B4/B5 table, pass B2 through. The result MUST
   be the original AEP Event, bit-for-bit modulo JSON member order.
2. Unknown `aep*`-prefixed extension attributes on inbound events MUST be preserved
   when their names are legal AEP extension names (AEP-0001 §5.4). They may be
   from a newer minor version, and any attribute a conformant newer-minor bridge
   mints satisfies the grammar by construction, since the extension grammar is
   version-stable. A name violating the shared grammar cannot ride the envelope
   (the envelope schema rejects it) and is dropped with a diagnostic like any
   other illegal name.

   Other unknown CE extension attributes SHOULD be preserved as AEP extension
   attributes when their names are legal (AEP-0001 §5.4), otherwise dropped with
   a diagnostic.
3. **Foreign CE events** (any other `type`) are out of scope: the bridge is an
   AEP-to-CE profile carrier, not a universal CE ingester. Lifting foreign CE traffic
   into AEP is an adapter's job (`custom`/`x.*`), like any other source.

## 3. OTLP export (AEP → OpenTelemetry)

### 3.1 Signal

AEP Events export as **OTLP LogRecords** (the OTel Events model: LogRecord +
`event_name`), not synthesized spans. Span synthesis (session/run/step spans from
start/finish pairs via `cause`) is a possible future exporter mode: additive,
deferred (an open question for a future revision).

### 3.2 Field mapping

| AEP | OTLP LogRecord | Note |
|---|---|---|
| `time` | `time_unix_nano` | RFC 3339 → epoch nanos |
| `type` | `event_name` = `"dev.aep." + type` | same string as the CE bridge: one exported identity everywhere |
| `severity` | `severity_text` + `severity_number` | table §3.4 |
| `traceparent` | `trace_id`, `span_id`, `flags` | parsed W3C fields; Events land *on* existing traces |
| `agent` | resource attribute `service.name` (default) + attribute `aep.agent_id` | resource = the emitting agent process; override for shared relays |
| `session` / `run` / `step` | attributes `aep.session_id`, `aep.run_id`, `aep.step` | |
| `id` / `seq` / `epoch` / `cause` / `capture` | attributes `aep.event_id`, `aep.sequence`, `aep.epoch`, `aep.causation_id`, `aep.capture` | |
| `subject` / `source` | attributes `aep.subject`, `aep.source` | |
| `data` members named `gen_ai.*` | **top-level LogRecord attributes, verbatim** | the OTel-mirror rule's payoff: token counts, model names light up in backends with zero mapping code |
| remaining `data` | `body` (AnyValue map) | preserved wholesale |

### 3.3 Capture enforcement at the exporter

The exporter takes a `max_capture` setting (default `metadata`). Events above it are
down-leveled per AEP-0001 §8.2 / AEP-0002 §5.1 **before** export; `capture: none`
Events export envelope-derived attributes only (no `body`). Backends never see what
the operator did not opt into.

### 3.4 Severity mapping

| AEP | `severity_text` | `severity_number` |
|---|---|---|
| `debug` | DEBUG | 5 |
| `info` | INFO | 9 |
| `notice` | INFO2 | 10 |
| `warning` | WARN | 13 |
| `error` | ERROR | 17 |
| `critical` | FATAL | 21 |

The inverse buckets OTLP ranges back to the six AEP levels
(1-8 to `debug`, 9 to `info`, 10-12 to `notice`, 13-16 to `warning`, 17-20 to
`error`, 21-24 to `critical`).

### 3.5 Exporter shape

`aep-bridge-otlp` is an ordinary AEP **consumer**: it subscribes with `attr-match`
filters (operator-scoped), batches, and emits OTLP/HTTP. It is an ecosystem
consumer, never normative infrastructure.

## 4. OCSF projection (AEP → OCSF)

### 4.1 Signal and scope

AEP's security-relevant event families project onto **OCSF** (Open
Cybersecurity Schema Framework) event classes, so security-analytics backends
that ingest OCSF natively (SIEM and security-lake deployments) can consume AEP
fleets with zero mapping code. The projection targets OCSF version **1.8.0**
(pinned; re-checked per AEP release, §5).

Three AEP families project; every other event (the control plane, progress,
presence, messages, vendor `x.*` traffic) deliberately produces **no OCSF
record**. This is a scope decision, not a loss: the OTLP export (§3) is the
full-fidelity telemetry channel, the OCSF projection is the security-signal
channel, and flooding a security pipeline with non-security agent chatter under
classes that do not mean it would be a mis-mapping.

Like the OTLP exporter (§3.5), an OCSF forwarder is an ordinary AEP consumer;
the mapping below is normative independent of any runtime.

### 4.2 Class mapping

| AEP event | OCSF class | `activity_id` | `status_id` | `disposition_id` |
|---|---|---|---|---|
| `session.started` | Authentication (3002) | 1 Logon | 1 Success | n/a |
| `session.ended` | Authentication (3002) | 2 Logoff | 1 Success | n/a |
| `tool.requested` | Process Activity (1007) | 1 Launch | n/a | n/a |
| `tool.completed` | Process Activity (1007) | 2 Terminate | 1 Success | n/a |
| `tool.failed` | Process Activity (1007) | 2 Terminate | 2 Failure | n/a |
| `tool.denied` | Process Activity (1007) | 1 Launch | 2 Failure | 2 Blocked |
| `attention.requested` | Detection Finding (2004) | 1 Create | 1 New | n/a |
| `attention.answered` | Detection Finding (2004) | 2 Update | 2 In Progress | n/a |
| `attention.resolved` | Detection Finding (2004) | 3 Close | 4 Resolved | see §4.5 |
| `attention.timeout` | Detection Finding (2004) | 3 Close | n/a | 16 No Action |

`category_uid` is the class's OCSF category (3 IAM, 1 System Activity,
2 Findings); `type_uid := class_uid * 100 + activity_id`. All Detection Finding
records of one attention lifecycle share one finding identity:
`finding_info.uid` is the `attention.requested` Event's `id`, its own `id` on
the request, the `subject` on every later lifecycle Event (the stable
correlation AEP-0002 §5 guarantees).

### 4.3 Record construction

Every projected record is derived from the envelope plus the
capture-surviving payload, and nothing else:

- `time` := `time` as epoch milliseconds; `metadata` := `{version:` the pinned
  OCSF version, `uid: id, original_time: time, product: {name: agent}}`, plus
  `correlation_uid := cause` when present. When the record asserts a
  `disposition_id` it declares `metadata.profiles: ["security_control"]` (the
  disposition attribute rides that OCSF profile).
- `severity_id` per the §4.4 table; `message := type`.
- **Authentication:** `user := {name: agent}`, `session := {uid: session}`.
- **Process Activity:** `actor := {app_name: agent}`; `process := {name:   subject}` plus `uid :=` the tool call id when the surviving payload carries
  it; `device := {type_id: 0}` plus `hostname :=` the `source` URI authority
  when it has one, else `name := source` verbatim.
- **Detection Finding:** `finding_info := {uid:` the request id (§4.2)`,   title: type}`.
- **Fidelity carrier:** `unmapped.aep` := the complete envelope verbatim
  (every attribute except `data`, extension attributes included);
  `unmapped.data` := the capture-surviving payload verbatim (omitted when
  empty). A consumer can always recover the full AEP identity from the record.

Records satisfy the base-class required attributes of the pinned OCSF version
by construction. One documented edge: when a tool Event's payload does not
survive the ceiling (`capture: none`, or a level above `max_capture`), the
`process` object carries only the tool name and cannot satisfy OCSF's
at-least-one-of `pid`/`uid`/`cpid` object constraint; the record still ships,
and the projection never invents an identifier to green a constraint.

### 4.4 Severity mapping

| AEP | OCSF `severity_id` |
|---|---|
| `debug` | 1 Informational |
| `info` | 1 Informational |
| `notice` | 2 Low |
| `warning` | 3 Medium |
| `error` | 4 High |
| `critical` | 5 Critical |

Monotone, and lossy only at `debug`/`info` (OCSF has no debug severity); the
original severity string always rides `unmapped.aep.severity`. The mapping is
one-way: there is no OCSF-inbound direction.

### 4.5 Dispositions assert only what the wire asserts

A disposition is claimed exactly where an AEP Event itself asserts the
outcome: `tool.denied` → 2 Blocked, `attention.resolved` with
`resolution: dismissed` → 16 No Action, `attention.timeout` → 16 No Action
(the request expired unanswered; AEP-0002 §5 makes it terminal). An
`attention.resolved` with `resolution: answered` closes its finding with **no
`disposition_id`**: the decision content lives in the answer payload and its
interpretation is vendor-scoped (AEP-0008); a projection claiming
Allowed or Blocked there would assert knowledge the wire does not carry.

### 4.6 Capture enforcement

The §3.3 rule applies unchanged: a `max_capture` setting (default `metadata`)
down-levels Events before projection, and `unmapped.data` carries only what
survives. Because `attention.requested` defaults to `capture: redacted`, at
the default ceiling a finding is created from the envelope alone: prompt
text, options, and form fields never reach a security backend the operator
did not opt into.

## 5. Conformance

1. **Round-trip identity:** for every golden fixture Event `e`,
   `ce_inverse(ce_forward(e)) ≡ e` (canonical-JSON comparison). This fixture class
   MUST stay green in CI for any release.
2. **Validity:** `ce_forward(e)` MUST validate as CloudEvents 1.0 JSON format
   (attribute-name/type legality included).
3. **OTLP mapping:** golden Events have pinned expected LogRecord projections
   (JSON encoding of the OTLP data model); severity and `gen_ai.*` hoisting are
   asserted exactly; `max_capture` down-leveling is asserted.
4. Bridge mappings and fixtures MUST ship with v0.1 (implementations may be
   labelled beta; the mappings and fixtures may not). The bridge annexes are
   versioned with the specification; the external pins, namely OTel severity/name
   and the OCSF class subset (`conformance/fixtures/ocsf/ocsf-classes.json`:
   attribute-name sets and the activity/status/disposition/severity value
   sets at the pinned OCSF version), are re-checked per AEP release.
5. **Inbound preserve-path:** hand-authored inbound CE events pin §2 rule 2
   directly: a grammar-legal `aep*`-prefixed extension attribute is preserved,
   a plain legal extension name is preserved, and a name violating AEP-0001
   §5.4's grammar is dropped while the rest maps
   (`conformance/fixtures/ce-roundtrip/inbound-cases.json`; both runners feed
   their inverse implementation directly: the golden-derived round-trip
   fixtures cannot exercise an attribute the golden corpus does not carry).
6. **OCSF projection:** golden Events in the §4.2 families have pinned
   expected records (`conformance/fixtures/ocsf/ocsf-expected.json`); the
   severity table, the §4.5 disposition rules (including the
   answered-carries-no-disposition rule), and capture enforcement are asserted
   exactly, and every pinned record is checked against the pinned OCSF class
   subset: only class attributes, enum-legal ids, derived `type_uid`.

## 6. Security considerations

*This section is informative; every rule it mentions is defined where cited.*

- **Bridges are the egress boundary of the capture model.** Everything exported
  lands in systems with their own retention, indexing, and access-control
  regimes that AEP's `capture` semantics no longer govern. For OTLP and OCSF the
  control point is normative: the exporter's `max_capture` (default `metadata`)
  down-levels before export, so backends never see what the operator did not opt
  into (§3.3, §4.6).

  A CloudEvents forwarder has no section of its own here because it needs none.
  It is an ordinary re-emitting hop, and the down-level rule of AEP-0001 §8.2
  applies to it like any hop; a deployment configures a ceiling there with the
  same care as at the OTLP exporter.
- **Envelope metadata travels as broker-visible attributes.** The B4 mapping
  puts session identifiers, agent identity, sequence positions, and causation
  links into CE extension attributes (§1): exactly the part of a message that
  broker ecosystems most freely log, index, route on, and hand to middleware.
  The same holds for the OTLP attribute projection (§3.2).

  Operators for whom activity *structure* is sensitive (who ran what, when, how
  much; visible even at `capture: metadata`, AEP-0001 §12) treat `aep*`
  attributes as data when configuring broker ACLs and telemetry pipelines, not
  as mere routing surface.
- **Severity crosses the bridge with emitter trust attached.** The §3.4 mapping is
  monotone and adds no interpretation: downstream alerting fires on the severity
  the emitter asserted (AEP-0002 §8). The inverse direction buckets OTLP's 24
  levels into six (order-preserving but lossy within a bucket), so round-tripped
  severity is a coarser claim than the original.
- **Payload members surface as top-level attributes.** The `gen_ai.*` hoisting rule
  (§3.2) deliberately promotes those payload fields to LogRecord attributes so
  backends index them without mapping code. They are `metadata`-level by schema
  (AEP-0002 §5.2): token counts and model names, not free text. But they are
  payload-derived and producer-controlled, and inherit the untrusted-input posture
  of AEP-0001 §12 wherever attributes feed queries or dashboards.
- **OCSF records feed detection pipelines: dispositions are claims.** A
  disposition in a security backend reads as an enforcement fact, which is why
  §4.5 claims one only where the AEP wire itself asserts the outcome and never
  interprets an answered decision. The projection carries the emitter's
  severity and outcome assertions with the emitter's trust attached, exactly
  like the OTLP direction; `unmapped.aep` preserves the evidence trail back to
  the source Event.

## References

- AEP-0001, AEP-0002.
- CloudEvents 1.0 core + JSON format + Distributed Tracing extension.
- OTLP `logs/v1` protobuf (LogRecord).
- OTel Logs Data Model severity ranges.
- W3C Trace Context.
- OTel GenAI semantic conventions (mirrored names).
- OCSF 1.8.0 (Authentication 3002, Process Activity 1007, Detection Finding
  2004, the `security_control` profile).
