# 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.