# agents.md — drasi-core/lib/src/channels

## FFI Boundary Warning

Several types in this directory cross the dynamic plugin FFI boundary. Rich
`repr(Rust)` event payloads (`SourceEventWrapper`, `BootstrapEvent`,
`QueryResult`) cross **by value as serialized MessagePack bytes** (issue #602) —
they must keep their `serde` derives. Simple enums are mapped to `#[repr(C)]`
mirrors.

> **Do not** transfer these `repr(Rust)` types as opaque `Box::into_raw` /
> `Box::from_raw` pointers: `repr(Rust)` has no stable cross-cdylib layout and
> `bytes::Bytes`/`Arc<str>` carry module-local pointers, which caused #602's heap
> corruption. The SDK version check validates only the `#[repr(C)]` envelope ABI,
> not the payload layout.

### Types that cross FFI

| Type | File | How it crosses | FFI wrapper |
|------|------|---------------|-------------|
| `SourceEventWrapper` | `events.rs` | Serialized `SourceEventPayload` bytes | `FfiSourceEvent.payload_ptr` |
| `BootstrapEvent` | `events.rs` | Serialized `BootstrapEventPayload` bytes | `FfiBootstrapEvent.payload_ptr` |
| `QueryResult` | `events.rs` | Serialized `QueryResult` bytes | `FfiQueryResult.payload_ptr` |
| `SubscriptionResponse` | `events.rs` | Converted field-by-field to `FfiSubscriptionResponse` | `FfiSubscriptionResponse` |
| `ComponentStatus` | `events.rs` | Mapped to `FfiComponentStatus` enum | `FfiComponentStatus` |
| `DispatchMode` | `dispatcher.rs` | Mapped to `FfiDispatchMode` enum | `FfiDispatchMode` |

### What to update when changing these types

#### Adding/removing fields on `SourceEventWrapper` or `BootstrapEvent`

These cross as opaque pointers, so field changes don't break the FFI envelope layout.
However, you must still:

1. Bump `FFI_SDK_VERSION` in `components/plugin-sdk/src/ffi/metadata.rs` — the host and
   plugin must agree on the struct layout since they both cast the same `*mut c_void`.

2. If metadata fields are extracted (e.g., `source_id` from `SourceEventWrapper`), update
   the extraction in `components/plugin-sdk/src/ffi/vtable_gen.rs`.

#### Adding/removing fields on `QueryResult`

`QueryResult` crosses FFI as an opaque pointer via `ReactionVtable.enqueue_query_result_fn`.
The host boxes the value (`Box::into_raw`) and the plugin takes ownership (`Box::from_raw`).

1. Bump `FFI_SDK_VERSION` in `components/plugin-sdk/src/ffi/metadata.rs` — both sides
   cast the same `*mut c_void` pointer.

2. **Plugin side** — `components/plugin-sdk/src/ffi/vtable_gen.rs` — the
   `enqueue_query_result_fn` function in both `build_reaction_vtable()` and
   `build_reaction_vtable_from_boxed()` reconstructs the `QueryResult`.

3. **Host side** — `components/host-sdk/src/proxies/reaction.rs` — `ReactionProxy`
   boxes the `QueryResult` and passes the raw pointer.

#### Adding variants to `ComponentStatus` or `DispatchMode`

1. **FFI enum** — Add the variant to the corresponding `#[repr(C)]` enum in
   `components/plugin-sdk/src/ffi/types.rs` (`FfiComponentStatus` or `FfiDispatchMode`)

2. **Plugin SDK mapping** — Update the match arms in
   `components/plugin-sdk/src/ffi/vtable_gen.rs` that convert between the Rust enum
   and the FFI enum

3. **Host SDK mapping** — Update the match arms in the corresponding proxy files:
   - `ComponentStatus`: `components/host-sdk/src/proxies/source.rs` and
     `components/host-sdk/src/proxies/reaction.rs` (both have `status()` methods)
   - `DispatchMode`: `components/host-sdk/src/proxies/source.rs` (`dispatch_mode()`)

4. **Version bump** — Bump `FFI_SDK_VERSION` minor version

#### Changing `SubscriptionResponse`

This struct is deconstructed into FFI parts on the plugin side and reconstructed on
the host side:

1. **Plugin side** — `components/plugin-sdk/src/ffi/vtable_gen.rs` — look for
   `FfiSubscriptionResponse` construction (the `subscribe` vtable function)

2. **FFI struct** — `components/plugin-sdk/src/ffi/vtables.rs` — update
   `FfiSubscriptionResponse` definition

3. **Host side** — `components/host-sdk/src/proxies/source.rs` — the `subscribe()`
   method on `SourceProxy` reconstructs `SubscriptionResponse` from FFI parts

4. **Host side receivers** — `components/host-sdk/src/proxies/change_receiver.rs` —
   `ChangeReceiverProxy` and `BootstrapReceiverProxy` reconstruct the channel types
