LQL today exposes a single fixed session reader: `current_user_id()`, which expands to `current_setting('rls.current_user_id', true)` on Postgres and a row from the `__rls_context` table on SQLite (per Migration/README.md).
NAP needs two distinct session-scoped values: `app.tenant_id` and `app.user_id`. Both are set per-transaction via `SET LOCAL app.tenant_id = '...'` and consumed by RLS policies through helper functions:
- `app_tenant_id() RETURNS uuid` → `SELECT NULLIF(current_setting('app.tenant_id', true), '')::uuid`
- `app_user_id() RETURNS uuid` → same shape against `app.user_id`
These two helpers are the only reason NAP needs SQL function bodies at all. Every other function (`is_member`, `is_tenant_writer`, `is_tenant_owner`) is just an EXISTS check that LQL's `exists(pipeline)` already expresses cleanly — once #(LQL function body issue) lands and once those bodies can call a portable session-reader builtin.
NAP request: add a generic LQL builtin that accepts a session-context key:
```yaml
functions:
- name: app_tenant_id
returns: uuid
bodyLql: current_setting('app.tenant_id')::uuid
- name: app_user_id
returns: uuid
bodyLql: current_setting('app.user_id')::uuid
```
Postgres transpiles to `current_setting('app.tenant_id', true)::uuid` (the `true` flag ⇒ NULL when missing instead of erroring). SQLite transpiles to a lookup against the `__rls_context` table — the namespace+key pair becomes the row id.
Acceptance:
- LQL function: `current_setting()` → returns text, callable from `bodyLql:` and from policy `using:` / `withCheck:` predicates.
- Postgres emission: `current_setting('namespace.key', true)` (always missing-OK; NULL on absence).
- Cast support: composes with the existing LQL cast operator (`::uuid`, etc.) so callers can express `current_setting('app.tenant_id')::uuid` directly.
- SQLite emission deferred until SQLite RLS emulation needs it.
NAP Tier 1 alongside the LQL-function-body issue. Together they let NAP delete the last raw SQL from `migrations/schema.yaml`.
LQL today exposes a single fixed session reader: `current_user_id()`, which expands to `current_setting('rls.current_user_id', true)` on Postgres and a row from the `__rls_context` table on SQLite (per Migration/README.md).
NAP needs two distinct session-scoped values: `app.tenant_id` and `app.user_id`. Both are set per-transaction via `SET LOCAL app.tenant_id = '...'` and consumed by RLS policies through helper functions:
These two helpers are the only reason NAP needs SQL function bodies at all. Every other function (`is_member`, `is_tenant_writer`, `is_tenant_owner`) is just an EXISTS check that LQL's `exists(pipeline)` already expresses cleanly — once #(LQL function body issue) lands and once those bodies can call a portable session-reader builtin.
NAP request: add a generic LQL builtin that accepts a session-context key:
```yaml
functions:
returns: uuid
bodyLql: current_setting('app.tenant_id')::uuid
returns: uuid
bodyLql: current_setting('app.user_id')::uuid
```
Postgres transpiles to `current_setting('app.tenant_id', true)::uuid` (the `true` flag ⇒ NULL when missing instead of erroring). SQLite transpiles to a lookup against the `__rls_context` table — the namespace+key pair becomes the row id.
Acceptance:
NAP Tier 1 alongside the LQL-function-body issue. Together they let NAP delete the last raw SQL from `migrations/schema.yaml`.