# v0.87.7 - Security Context and Catalog Foundation

> **Status:** Planned
> **Scope:** 6 person-weeks
> **User promise:** *"Lifecycle APIs never lend extension-owner privileges to defining SQL."*
> **Blocked by:** [v0.87.6](v0.87.6.md)
> **Program:** [Lifecycle security reimplementation](../plans/pg_trickle_lifecycle_security_reimplementation_plan.md)

## Theme

Build one audited boundary for original-caller capture and stream-owner
execution before any public function becomes `SECURITY DEFINER`. Persist the
defining query's exact resolution path so manual and background refreshes use
the same names that creation used.

This release changes no public lifecycle function attributes. It supplies the
foundation and proves that success, nested calls, Rust errors, and PostgreSQL
errors restore the backend's prior role and GUC state.

## Scope allocation

| Work package | Person-weeks |
|---|---:|
| Caller identity and exact search-path capture | 1.25 |
| Restricted stream-owner execution and restoration | 1.50 |
| `defining_search_path` catalog and upgrade migration | 1.25 |
| Unit, fuzz, PostgreSQL, upgrade, and review work | 2.00 |
| **Total** | **6.00** |

## Items

### LSEC-1: Typed caller and owner contexts

Add `CallerContext` and `StreamExecutionContext`. Definer entries use
`GetOuterUserId()` and the saved pre-function path. Invoker entries read the
active path. Expand only a standalone `"$user"` element, including quoted role
names, commas inside identifiers, escaped quotes, and whitespace.

Keep PostgreSQL identity-switching `unsafe` code in one module. Every block has
a precise `// SAFETY:` comment. Fail closed when the expected GUC stack state
is absent.

### LSEC-2: Restricted stream-owner execution

Add one internal helper backed by PostgreSQL 18's untrusted-user context. It
sets the stored path and `row_security = on`, runs through PostgreSQL error
capture, and restores role, security flags, path, and GUC nesting in all exit
paths. The role must come from canonical stream metadata, never a caller string.

### LSEC-3: Defining-path catalog contract

Add `defining_search_path TEXT NOT NULL` to the private stream-table catalog.
Creation and query-changing alter paths replace it. Configuration-only changes
and ownership transfer preserve it. Backfill legacy rows from the current
storage owner and fail the upgrade if a storage relation is missing.

## Required tests

- Pure unit and property tests cover all path grammar cases, empty elements,
  malformed quoting, and deterministic normalization.
- PostgreSQL-backed tests cover success, nested contexts, Rust errors,
  PostgreSQL `ERROR`, RLS-on behavior, and backend reuse after failure.
- Negative probes verify that owner code cannot read extension-owner-only data
  or create objects in `pgtrickle`.
- Fresh-install and v0.87.6 upgrade tests compare catalog shape, values,
  constraints, and generated SQL.
- The unsafe inventory and static analysis must report only reviewed additions.

## Exit criteria

- [ ] Original caller identity and exact path survive nested definer calls.
- [ ] Every exit path restores role, security flags, path, GUC nesting, and
      backend usability.
- [ ] Legacy rows receive a deterministic owner-derived path or the upgrade
      fails before mutation.
- [ ] Fresh installation and upgraded installation have identical catalogs.
- [ ] No public lifecycle function becomes a definer in this release.
- [ ] Unit, PostgreSQL, upgrade, unsafe, and full repository CI gates pass.
