> **Plain-language companion:** [v0.46.0.md](v0.46.0.md)

## v0.46.0 — Extract `pg_tide`: standalone transactional outbox, inbox, and relay

**Status: Released.** Full design in
[plans/PLAN_RELAY_STANDALONE.md](../plans/PLAN_RELAY_STANDALONE.md) §7.

> **Release Theme**
> Extract the outbox, inbox, and relay subsystems from `pg_trickle` into a
> new standalone extension `pg_tide` (`trickle-labs/pg-tide`). `pg_trickle`
> is left shipping exactly one thing: incremental view maintenance. All event
> messaging surface moves to a product that is useful independently.

---

### Motivation

The outbox/inbox/relay code (~6,150 Rust LOC, ~2,500 SQL LOC) has lived inside
`pg_trickle` since v0.28–v0.29. It works, but it is an architectural mismatch:

- Different audience (microservices engineers vs. IVM users)
- Different release cadence (relay backends evolve independently of DVM)
- Different deployment story (`pg_tide` + binary; `pg_trickle` extension only)
- ~6 optional async messaging crates + AWS SDK in the extension's `Cargo.lock`

The feature set has no known production users at the time of the cut, making
this a clean break with no backwards-compatibility obligation.

See [plans/PLAN_RELAY_STANDALONE.md](../plans/PLAN_RELAY_STANDALONE.md) §7.8
for the full rationale.

---

### What ships in the new `pg_tide` repo

**Extension (schema `tide`):**

| API | Description |
|-----|-------------|
| `tide.outbox_create(name, ...)` | Create a named outbox table with retention config and claim-check threshold |
| `tide.outbox_publish(name, payload, headers)` | Write a row transactionally from inside any application transaction |
| `tide.outbox_status(name)` | View lag, consumer offsets, and claim-check pressure |
| `tide.outbox_disable(name)` | Pause publishing without dropping the table |
| `tide.inbox_create(name, ...)` | Create an idempotent inbox with optional DLQ, priority, ordering, retention |
| `tide.inbox_mark_processed(name, event_id)` | Acknowledge successful processing |
| `tide.inbox_mark_failed(name, event_id, error)` | Send to DLQ after max retries |
| `tide.replay_inbox_messages(name, ...)` | Re-queue events from DLQ |
| `tide.relay_set_outbox(name, config)` | Configure an outbox pipeline |
| `tide.relay_set_inbox(name, config)` | Configure an inbox pipeline |
| `tide.relay_enable/disable/delete(name)` | Lifecycle management |
| `tide.relay_list_configs()` | Inspect active pipelines |
| `tide.<name>_pending` / `_dlq` / `_stats` | Plain VIEWs — no IVM overhead |

**Binary (`pg-tide`):**

- Lifted verbatim from `pgtrickle-relay/` using `git filter-repo` to preserve
  history.
- Six source backends: NATS, Kafka, outbox poller, webhook, Redis, SQS,
  RabbitMQ.
- Eight sink backends: same set + stdout + pg-inbox.
- Advisory-lock leader election, independent metrics/health server (Axum +
  Prometheus).

**No `pg_trickle` required:** `pg_tide.control` has no `requires`. The inbox
helper views are plain VIEWs; the one integration point is `attach_outbox()`
on the `pg_trickle` side (see below).

---

### Changes in `pg_trickle` (this repo)

| Item | Change |
|------|--------|
| `src/api/outbox.rs` (1,048 LOC) | Deleted |
| `src/api/inbox.rs` (1,215 LOC) | Deleted |
| Relay catalog SQL in `src/lib.rs` (~240 LOC) | Deleted |
| `pgtrickle-relay/` crate (~3,650 LOC) | Moved to `trickle-labs/pg-tide` |
| `Dockerfile.relay` | Deleted |
| 4 relay publish jobs in `release.yml` | Deleted |
| `aws-smithy-http-client` exception in `security.yml` | Deleted |
| `pgtrickle-relay` in workspace `Cargo.toml` | Removed |
| `pgtrickle.enable_outbox()` / `pgtrickle.create_inbox()` | Replaced by `pgtrickle.attach_outbox()` |
| New `pgtrickle.attach_outbox(stream_table, ...)` | Calls `tide.outbox_create()` and registers a refresh-time SPI hook to `tide.outbox_publish()` — same transaction, ADR-001/ADR-002 atomicity preserved |

**Upgrade script** (`pg_trickle--0.45.0--0.46.0.sql`) drops all old
`pgtrickle.relay_*` objects and all old outbox/inbox API functions. Base
tables (`pgtrickle.outbox_<st>`, `pgtrickle.inbox_<...>`) are left in place
for manual data migration; the CHANGELOG documents the explicit equivalents in
`pg_tide`.

---

### Integration point: `attach_outbox()` and atomicity

The critical invariant from ADR-001/ADR-002: the outbox row and the refresh
MERGE must commit in the same transaction. After the cut:

1. `pgtrickle.attach_outbox('orders_stream', ...)` calls
   `tide.outbox_create('outbox_orders_stream', ...)` once at setup time.
2. Inside each refresh, the hot path calls
   `SELECT tide.outbox_publish($1, $2, $3)` via SPI. The SPI call runs in the
   current transaction — same atomicity guarantee as a direct INSERT.
3. `pg_trickle.control` does **not** declare `requires = 'pg_tide'`. At
   `attach_outbox()` call time, the function probes
   `to_regproc('tide.outbox_create(...)')` and raises with an actionable
   install hint if `pg_tide` is absent. All other IVM features (refresh,
   CDC, scheduler, DAG) are unaffected.

---

### Work plan (6 working days)

See [plans/PLAN_RELAY_STANDALONE.md §7.9](../plans/PLAN_RELAY_STANDALONE.md)
for the day-by-day breakdown.

**Days 1–2:** Bootstrap `trickle-labs/pg-tide`; lift outbox/inbox into the
extension; carry over tests.
**Day 3:** Lift relay catalog SQL and `pgtrickle-relay/` binary into the new
repo.
**Day 4:** Delete moved code from this repo; add `attach_outbox()`;
write the upgrade script.
**Day 5a:** Documentation — move and rewrite the four outbox/inbox/relay docs;
update cross-references in ~12 docs in this repo.
**Day 5b:** CI / packaging — clone CI templates into `pg-tide`; verify both
repos build clean; cut pre-release tags.

**Pre-cut action (Day 3 gate):** Post a one-line announcement to the
`pg_trickle` discussions board confirming no known users of
`enable_outbox()` / `create_inbox()` / the relay binary.

---

### Success criteria

- `cargo build -p pg_trickle` pulls no relay-related dependency.
- A user installing only `pg_tide` (without `pg_trickle`) can configure a
  NATS-to-webhook pipeline and run it end-to-end against vanilla Postgres 18.
- `pgtrickle.relay_*` tables and functions no longer exist in a fresh install.
- `pgtrickle.attach_outbox()` integration test passes with `pg_tide` installed
  and raises a clear error without it.
- Total LOC removed from this repo: ≥6,150 Rust + ≥2,500 SQL.

---

### Assets moving to `trickle-labs/pg-tide`

See [plans/PLAN_RELAY_STANDALONE.md §7.10](../plans/PLAN_RELAY_STANDALONE.md)
for the full inventory:

- `docs/OUTBOX.md`, `docs/INBOX.md`, `docs/RELAY.md`, `docs/RELAY_GUIDE.md`
- `tests/e2e_outbox_tests.rs`, `tests/e2e_inbox_tests.rs`
- `plans/relay/PLAN_RELAY_WIRE_FORMATS.md`, `PLAN_RELAY_CLI.md`,
  `PLAN_RELAY_CLI_PHASE_2.md`, `PLAN_RELAY_GAPS_FROM_FELDERA_RISINGWAVE.md`
- Blog posts: `built-in-outbox.md`, `outbox-pattern-turbocharged.md`,
  `inbox-pattern-kafka.md`, `relay-deep-dive.md`
- CI templates to clone (not move): pgrx composite action, skill files,
  AGENTS.md, lint/test/coverage/release workflows, justfile, deny.toml,
  stability-test workflow

---

*Previous: [v0.45.0 — Operational Readiness, Scalability & CI Completeness](v0.45.0.md)*
*Next: [v0.47.0 — Embedding Pipeline Infrastructure & ANN Maintenance](v0.47.0.md)*
