# Channel Mapping (/connectors/gcp-pub-sub/reference/channel-mapping)



This is the master reference for how the embedded KubeMQ Pub/Sub connector maps Pub/Sub topics and
subscriptions onto KubeMQ. A topic is backed by exactly one KubeMQ **Events Store** log; each
subscription is backed by its own KubeMQ **Queue** channel. This deterministic mapping is the
contract that makes cross-protocol interop work — a Pub/Sub `Publish` and a native KubeMQ consume
meet on the same channel.

## Resource → channel table [#resource--channel-table]

| Pub/Sub resource                              | KubeMQ object    | Channel        |
| --------------------------------------------- | ---------------- | -------------- |
| Topic `projects/{p}/topics/{t}`               | Events Store log | `gcp.{t}`      |
| Subscription `projects/{p}/subscriptions/{s}` | Queue            | `gcp.sub.{s}`  |
| Snapshot / Schema                             | Registry record  | — (no channel) |

The `{p}` **project segment is parsed and validated but ignored** — the connector is single-tenant,
like the emulator, so resource ids are **global across projects**. Two clients using different
project ids but the same topic id share one `gcp.{t}` log.

## Topic grammar [#topic-grammar]

A topic maps to exactly one KubeMQ Events Store log:

```text
gcp.{t}
└┬┘ └┬┘
 │   └─ the topic id (the bare id you pass to CreateTopic — NOT the projects/.../topics/ path)
 └─ fixed connector prefix ("gcp.")
```

| Pub/Sub topic | KubeMQ channel  |
| ------------- | --------------- |
| `orders`      | `gcp.orders`    |
| `events`      | `gcp.events`    |
| `audit-log`   | `gcp.audit-log` |

<Callout type="warn">
  **Topic ids may not start with `sub.`** (gotcha #7). `sub.` is the reserved namespace for the
  subscription queues below, so a topic id beginning `sub.` is rejected at create (`INVALID_ARGUMENT`).
  See [Limits & Rules](/connectors/gcp-pub-sub/reference/limits-and-rules) for the full id grammar.
</Callout>

## Subscription grammar [#subscription-grammar]

Each subscription maps to its own KubeMQ Queue channel:

```text
gcp.sub.{s}
└──┬───┘ └┬┘
   │      └─ the subscription id (bare, not the projects/.../subscriptions/ path)
   └─ fixed connector prefix + reserved sub-namespace ("gcp.sub.")
```

| Pub/Sub subscription | KubeMQ channel       |
| -------------------- | -------------------- |
| `orders-sub`         | `gcp.sub.orders-sub` |
| `analytics`          | `gcp.sub.analytics`  |

A subscription id may not contain the reserved `.k.` or `.h.` infixes — those are owned by the
per-key channel grammar below.

## Per-ordering-key channels [#per-ordering-key-channels]

When a subscription enables message ordering, the connector routes **each ordering key onto its own
queue channel** so the broker preserves per-key order natively. The base `gcp.sub.{s}` channel
continues to serve keyless messages:

```text
gcp.sub.{s}.k.{enc(key)}
└───┬────┘ └┬┘ └───┬────┘
    │       │      └─ the ordering key, percent-encoded
    │       └─ fixed ".k." per-key separator
    └─ the subscription's base queue channel
```

`enc` percent-encodes any byte outside `[a-zA-Z0-9_-]`. If the encoded channel name would exceed
its length budget, the connector falls back to a deterministic sha256 form
`gcp.sub.{s}.h.{hash}` — the same key always hashes to the same channel on every node, so per-key
order is still preserved (the fallback only couples multiple long keys onto one FIFO channel).

| Subscription | Ordering key      | KubeMQ channel                     |
| ------------ | ----------------- | ---------------------------------- |
| `orders-sub` | `tenant-a`        | `gcp.sub.orders-sub.k.tenant-a`    |
| `orders-sub` | `order/42`        | `gcp.sub.orders-sub.k.order%2F42`  |
| `orders-sub` | *(very long key)* | `gcp.sub.orders-sub.h.{sha256hex}` |

## Write-once-then-fan-out [#write-once-then-fan-out]

A `Publish` is **not** copied to every subscription on the wire. The connector:

1. **Writes once** to the topic log `gcp.{t}` via `Array.SendEventsStore` — the authoritative,
   cross-protocol, replayable copy and the source for `Seek`.
2. **Fans out** one queue copy per subscription via `Array.SendQueueMessage(gcp.sub.{s})`,
   **applying each subscription's filter** — a filtered-out message is never enqueued (≈ auto-acked).
   Detached subscriptions are skipped.

So the topic log holds the complete history; each subscription queue holds the filtered slice that
subscription still owes its consumers.

## Reserved tags [#reserved-tags]

A `PubsubMessage` becomes a KubeMQ message whose &#x2A;*body is `data`** and whose &#x2A;*Tags are the message
`attributes`** plus **three reserved tags** carried across the wire:

| Reserved tag           | Carries                                | Set by                 |
| ---------------------- | -------------------------------------- | ---------------------- |
| `_pubsub_message_id`   | the server-assigned message id         | connector on `Publish` |
| `_pubsub_publish_time` | the publish timestamp                  | connector on `Publish` |
| `_pubsub_ordering_key` | the ordering key (only if one was set) | connector on `Publish` |

<Callout type="info">
  **Native consumers see these tags; Pub/Sub clients do not.** When the connector delivers a message
  back to a Pub/Sub client it strips the three `_pubsub_*` tags out of `attributes` and surfaces them
  as the native fields (`messageId`, `publishTime`, `orderingKey`). A native KubeMQ consumer reading
  `gcp.{t}` reads them as ordinary Tags — this is how a cross-protocol consumer recovers the message
  id and publish time.
</Callout>

### Attribute ⇄ tag round-trip [#attribute--tag-round-trip]

| Pub/Sub field                    | KubeMQ Tag                        | Notes                                                                        |
| -------------------------------- | --------------------------------- | ---------------------------------------------------------------------------- |
| message `attribute {Name}`       | Tag `{Name}`                      | round-trips losslessly; ≤ 100 attrs, key ≤ 256 B (no `goog`), value ≤ 1024 B |
| `data`                           | message body                      | ≤ 10 MiB total per message                                                   |
| ordering key                     | `_pubsub_ordering_key` (reserved) | ≤ 1024 B                                                                     |
| *(server-assigned)* message id   | `_pubsub_message_id` (reserved)   |                                                                              |
| *(server-assigned)* publish time | `_pubsub_publish_time` (reserved) |                                                                              |

Attribute keys must not start with `goog` (Google's reserved prefix); the connector enforces this at
publish (`INVALID_ARGUMENT`). See [Limits & Rules](/connectors/gcp-pub-sub/reference/limits-and-rules).

## Cross-protocol interoperability [#cross-protocol-interoperability]

Because the topic log is a normal KubeMQ Events Store channel, a Pub/Sub `Publish` to topic `orders`
is consumable by a native gRPC/REST Events Store subscriber on channel `gcp.orders`, and a
subscription's backlog is a native Queue channel `gcp.sub.{s}`.

<Callout type="info">
  **Deterministic read.** Subscribe to the Events Store log with start policy `startAt = "new"`
  **before** issuing the Pub/Sub publish, so the published message is guaranteed in-window for the
  native consumer (no startup race). See
  [Cross-Protocol Interop](/connectors/gcp-pub-sub/concepts/cross-protocol-interop).
</Callout>

## The registry is authoritative [#the-registry-is-authoritative]

Topics, subscriptions, snapshots, and schemas live in a **per-node replicated registry**
(synchronized across cluster nodes with a last-writer-wins rule). Only resources created through the
Pub/Sub API are visible to the Pub/Sub surface — a `Pull` from a subscription that was never
`CreateSubscription`d returns `NOT_FOUND`. (A native client can still read the raw `gcp.{t}` log
directly regardless of the registry.)

## Related [#related]

<Cards>
  <Card title="Architecture" href="/connectors/gcp-pub-sub/concepts/architecture" description="The gRPC listener, the fan-out model, and the write-once-then-fan-out service map." />

  <Card title="Capabilities" href="/connectors/gcp-pub-sub/reference/capabilities" description="The 38 supported RPCs and the documented gotchas behind this mapping." />

  <Card title="Limits & Rules" href="/connectors/gcp-pub-sub/reference/limits-and-rules" description="The resource-id grammar and attribute limits this channel mapping enforces." />

  <Card title="Error Codes" href="/connectors/gcp-pub-sub/reference/error-codes" description="The gRPC status codes the channel and attribute mapping can raise." />
</Cards>
