# v0.87.12 - Publication Security

> **Status:** Implemented
> **Scope:** 6 person-weeks
> **User promise:** *"Publication APIs grant no authority beyond the caller's own database rights."*
> **Blocked by:** [v0.87.11](v0.87.11.md)
> **Program:** [Lifecycle security reimplementation](../plans/pg_trickle_lifecycle_security_reimplementation_plan.md)

## Theme

Treat publication management as a database capability, not an automatic
consequence of stream ownership. The caller must own the stream and hold the
database privilege PostgreSQL requires. Publication ownership and the private
binding remain explicit and verifiable.

Creation and drop separate caller-context PostgreSQL DDL from private catalog
bookkeeping. A stale, renamed, or recreated publication cannot match a binding
by name alone.

## Scope allocation

| Work package | Person-weeks |
|---|---:|
| Caller-equivalent creation and ownership | 1.25 |
| Provenance-bound drop and reconciliation | 1.25 |
| Upgrade and operational diagnostics | 1.00 |
| Security, rollback, concurrency, and upgrade tests | 2.50 |
| **Total** | **6.00** |

## Items

### LSEC-16: Caller-equivalent publication creation

Require stream ownership and caller-equivalent `CREATE` on the current
database. Run `CREATE PUBLICATION` under caller context when PostgreSQL permits
it. If a privileged substep is unavoidable, transfer ownership before private
registration and verify the owner in `pg_publication`.

### LSEC-17: Immutable publication binding

Record the publication OID, owner OID, stream OID, and expected relation set.
Drop and repair validate the live object against that binding. Name reuse,
rename, ownership transfer, or relation-set drift produces a specific error
before mutation.

### LSEC-18: Atomic private integration

Update pg_trickle's private binding in a separate definer phase within the same
transaction. Failed DDL, ownership transfer, verification, or catalog work
rolls back both the PostgreSQL object and private state.

## Required tests

- Creation covers stream owner versus non-owner, database `CREATE` grant and
  revocation, quoted names, conflicting names, and exact publication owner.
- Drop and repair cover owner mismatch, rename, recreate with the same name,
  relation-set drift, stale private bindings, and unrelated publications.
- Failure injection at each phase proves no orphan publication or private row
  survives.
- Concurrent create, drop, rename, and ownership transfer use deterministic
  barriers and produce one consistent final binding.
- Fresh-install and v0.87.11 upgrade tests verify function attributes, ACLs,
  provenance columns, old binding migration, and generated API policy.
- Minimal-grant tests prove that private catalog access is neither granted nor
  required.

## Exit criteria

- [x] Stream ownership alone cannot bypass database publication privileges.
- [x] The resulting publication owner matches the documented caller policy.
- [x] Bindings use live object identity and reject stale name reuse.
- [x] Public DDL and private bookkeeping commit or roll back together.
- [x] No publication path executes caller-selected SQL as extension owner.
- [x] Security, rollback, concurrency, upgrade, and full repository CI gates pass.
