Application access and governance
Exact SQL for endpoints, auth providers, application grants, query-driven rate limits, lifecycle, and introspection.
NYXDB operators can publish typed read queries for direct application execution or subscription. The SQL in this page creates the operator-owned contract; an application client invokes it through the experimental auth and endpoint wire operations documented in Browser and platform clients.
This surface is Experimental. Published endpoints do not make the native
listener safe for direct internet exposure. The server has no native TLS, and
omitting --require-auth leaves the operator SQL path open even when an
end-user auth provider exists. Treat --require-auth as mandatory for every
browser-facing or otherwise untrusted listener.
Complete claims-bound example
The provider and endpoint use operator-owned tables. Every table still follows the normal storage-policy requirement.
CREATE TABLE sessions (
token String,
user_id UInt64,
tenant_id UInt64,
role String,
expires_at UInt64,
PRIMARY KEY (token)
) SETTINGS mode='keyed', storage_policy='memory_data';
CREATE TABLE fills (
tenant_id UInt64,
symbol String,
px Float64,
qty UInt64
) SETTINGS mode='append', storage_policy='memory_data';
CREATE AUTH PROVIDER app_sessions AS
STREAM SELECT token, user_id, tenant_id, role
FROM sessions
WHERE expires_at > to_unix_timestamp(now());
CREATE ENDPOINT live_fills(symbol String)
AUTH CLAIMS (tenant_id UInt64)
USING app_sessions
AS STREAM SELECT symbol, px, qty
FROM fills
WHERE tenant_id = $auth.tenant_id
AND symbol = $symbol
SETTINGS max_subscribers_per_user=3,
max_rows_per_s=500,
max_bytes_per_s=65536,
max_executions_per_min=60;The client supplies only symbol. The engine takes tenant_id from the claims
row returned for the presented token. Sending another wire parameter cannot
replace $auth.tenant_id; it produces an arity error.
Auth providers
An auth provider maps one presented token to a claims row. Provider output names form the claim schema used by endpoints and invocable views.
Return a stable, non-secret user_id from every provider. The engine uses that
claim for subscriber attribution and per-user governance. Without it, the
presented bearer token becomes the fallback identity and can surface in
subscriber or governance telemetry; never use a bearer token as a display
identity.
Lookup provider
CREATE AUTH PROVIDER session_lookup AS
SELECT user_id, tenant_id, role
FROM sessions
WHERE token = $token
AND expires_at > to_unix_timestamp(now())
SETTINGS cache_ttl_ms=5000;Lookup-provider rules:
- the body is a one-shot
SELECTand must reference the reserved$token; - exactly one matching row authenticates the connection;
- every named output column is a claim; computed outputs require aliases;
- duplicate claim names are rejected;
cache_ttl_msdefaults to 5,000 ms;- the claims row is revalidated after its cache age when the connection invokes again; and
- zero rows, multiple rows, an unknown provider, and provider errors all become
the same end-user wire error:
auth failed.
Streaming auth gate
CREATE AUTH PROVIDER session_gate AS
STREAM SELECT token, user_id, tenant_id, role
FROM sessions
WHERE expires_at > to_unix_timestamp(now());Streaming-gate rules:
STREAM SELECTis mandatory for this form;- one output column must be named
token; it is the replica key and is not a claim; - the remaining named columns are the claims row;
- the body cannot use
$tokenor client parameters; cache_ttl_msis rejected because the live replica is the truth;- deleting a session, changing the predicate result, or passing a time-based expiry removes the token and revokes every connection it admitted; and
- replay restores the provider and its internal system-class subscription after a persistent restart.
The lookup form remains useful when materializing all valid sessions would be inappropriate. The streaming form provides event-driven revocation.
Changing claim values in a streaming-gate row affects future endpoint or view
invocations; it does not rebind an already running subscription. A live
subscription freezes its parameter bindings, user_id, tenant, and role for
its lifetime. To change any identity or authorization claim safely, remove the
old token so its connections are revoked, issue a new token, and require a fresh
connection and subscription.
Provider selection and lifecycle
With no registered provider, no application end user can complete OP_AUTH.
Creating a claimless endpoint does not create an end-user entry path.
An endpoint with claims must resolve one provider. If exactly one provider is
registered, USING may be omitted; the canonical persisted DDL records the
resolved provider. When several providers exist, USING provider_name is
required.
USING is a definition-time binding and schema-validation reference, not
an invocation-affinity check. At runtime, a session authenticated through any
provider can invoke the endpoint when it supplies compatible declared claim
names and types. Do not treat the provider name as an authorization boundary;
encode every tenant and visibility restriction in server-bound claims and the
stored query predicate.
SHOW AUTH PROVIDERS;
DROP AUTH PROVIDER session_lookup;SHOW AUTH PROVIDERS returns
name, claims, cache_ttl_ms, source, digest, uuid, version. A streaming provider
shows stream in the cache column. CREATE OR REPLACE AUTH PROVIDER is not a
current form. A provider referenced by an endpoint cannot be dropped. Dropping
a live gate after its dependencies are removed revokes the sessions it admitted.
Endpoints
The general form is:
CREATE [OR REPLACE] ENDPOINT endpoint_name(
parameter_name Type [, ...]
)
[AUTH CLAIMS (claim_name Type [, ...]) [USING auth_provider]]
AS [STREAM] SELECT ...
[SETTINGS key=value [, ...]];USING is part of the AUTH CLAIMS clause. A claimless endpoint cannot name
an auth provider.
Use $parameter_name for a declared client value and $auth.claim_name for a
declared server claim. Raw ordinal placeholders such as $1, the provider-only
$token, and undeclared named parameters are rejected in an endpoint body.
CREATE ENDPOINT fill_snapshot(symbol String)
AUTH CLAIMS (tenant_id UInt64)
USING app_sessions
AS SELECT symbol, px, qty
FROM fills
WHERE tenant_id = $auth.tenant_id
AND symbol = $symbol
SETTINGS max_executions_per_min=30,
timeout_ms=250,
max_result_rows=1000;AS SELECT is one-shot and is invoked with OP_ENDPOINT_EXEC. AS STREAM SELECT is live and is invoked with OP_ENDPOINT_SUB. Subscribing to a one-shot
endpoint is an invalid invocation shape.
CREATE OR REPLACE ENDPOINT retains the endpoint UUID and increments its
version. Plain CREATE rejects a duplicate name. Catalog DDL is journaled and
replayed when a persistent data directory is configured.
Replace and drop are not live-revocation operations. An existing
OP_ENDPOINT_SUB keeps its original query and frozen bindings after
CREATE OR REPLACE or DROP ENDPOINT; drop also removes the endpoint's
governor/metering state. Drain or terminate every live subscription before a
query, claim, provider, or limit change whose old behavior must stop. New
clients must reconnect and subscribe to the reviewed definition.
Static endpoint limits
All six recognized settings use an unsigned integer. An omitted setting and an
explicit 0 both mean unlimited for that dimension.
| Setting | Enforcement scope | Exact behavior |
|---|---|---|
max_subscribers_per_user | Live subscriptions per endpoint and application-user identity | Checked at subscribe admission. The slot is released on every normal or terminal detach. |
max_rows_per_s | Each end-user subscription | The governor samples delivered row deltas over a window of at least one second and terminates only the violating subscription. The initial snapshot is not counted as live row deltas. |
max_bytes_per_s | Each end-user subscription | Sampled over the same runtime window. Transport bytes include the initial replica bytes in the first window. |
max_executions_per_min | Sliding 60-second window per endpoint and user | Checked for one-shot execution and for the initial execution of a subscription. A denied attempt does not consume another slot. |
timeout_ms | One-shot OP_ENDPOINT_EXEC only | Composes with the global query deadline. An endpoint-owned deadline breach returns limit exceeded: timeout_ms. It does not govern a live subscription. |
max_result_rows | One-shot OP_ENDPOINT_EXEC only | Rejects an over-limit result with limit exceeded: max_result_rows; it never silently truncates the result. It does not govern a live subscription. |
Neither timeout_ms nor max_result_rows applies to subscription setup, its
initial snapshot, or subsequent deltas.
Operator and system-class work is exempt from end-user endpoint limits, though endpoint counters still include operator invocations and subscriptions.
Unknown endpoint settings are recorded with a warning for forward-compatible DDL replay; they are not enforcement controls. Use only the six keys above when the behavior is required.
Application grants and invocable views
An endpoint is the pre-authorized bundle. A logical view exposed through the same endpoint wire operations follows a different rule: default deny against the application grant catalog.
CREATE VIEW fills_for_symbol AS
SELECT symbol, px, qty
FROM fills
WHERE tenant_id = ${auth.tenant_id}
AND symbol = ${symbol:String};
GRANT read, subscribe
ON VIEW fills_for_symbol
TO ROLE 'customer';The auth provider conventionally returns a role claim. OP_ENDPOINT_EXEC
requires read; OP_ENDPOINT_SUB requires subscribe. The view may be invoked
by name or relation UUID. A supported CREATE OR REPLACE operation that keeps
the relation UUID also keeps the UUID-backed policy. Drop-and-create establishes
a new identity and therefore a new policy boundary.
Name resolution is endpoint-first. If an endpoint and logical view share a
name, OP_ENDPOINT_EXEC or OP_ENDPOINT_SUB selects the endpoint and does not
perform the expected view-grant check. Prevent collisions in published naming,
and use the relation UUID when a client must invoke a specific logical view
unambiguously.
SHOW GRANTS;
REVOKE subscribe
ON VIEW fills_for_symbol
FROM ROLE 'customer';Application grants can target TABLE, VIEW, or MATERIALIZED VIEW and accept
the closed verb vocabulary read, subscribe, insert, delete, and
definition. This is catalog and future-facing vocabulary: the current
OP_ENDPOINT_EXEC and OP_ENDPOINT_SUB paths can invoke only endpoints and
logical VIEW relations. Consequently, only a logical-view read or
subscribe grant is consumable on the end-user wire today. The wire does
not expose application-user SQL, table or materialized-view invocation,
insert, delete, or block-ingest operations.
Application grants protect authenticated end-user invocations. Named
non-admin operator-class OP_ENDPOINT_EXEC and OP_ENDPOINT_SUB calls instead
resolve the stored query's scanned base relations and enforce the separate
operator read or subscribe grants. Admin and default-open implicit operators
bypass that policy. Operator-class calls remain exempt from application endpoint
quotas, so operator and end-user authorization and metering must still be
reviewed as separate domains.
SHOW GRANTS and system.grants use
relation, relation_uuid, role, verbs. Do not confuse them with
SHOW OPERATOR GRANTS or system.operator_grants; the two catalogs share no
authorization state.
Query-driven rate limits
A rate limit is a mandatory streaming query with exactly two outputs:
- the application-user identity; and
- an integer allowance named or interpreted as the allowed value.
CREATE TABLE user_limits (
user_id UInt64,
role String,
trades_30d UInt64,
PRIMARY KEY (user_id)
) SETTINGS mode='keyed', storage_policy='memory_data';
CREATE RATE LIMIT trader_subscriptions
ON concurrent_subscriptions AS
STREAM SELECT user_id,
CASE
WHEN role = 'admin' THEN 0
WHEN trades_30d > 100 THEN 5
ELSE 1
END AS allowed
FROM user_limits;The legal dynamic dimensions are:
| Dimension | Applies to |
|---|---|
concurrent_subscriptions | Live endpoint subscriptions per endpoint/user pair |
rows_per_s | Each covered subscription at the next sampling window |
bytes_per_s | Each covered subscription at the next sampling window |
executions_per_min | The next endpoint execution admission |
The query body cannot use $token or client parameters. It must read a relation,
return exactly two columns, and produce an allowance that parses as a
non-negative UInt64. 0 means unlimited. A negative, malformed, or otherwise
invalid allowance row is silently skipped; that user then falls back to the
remaining dynamic coverage, the endpoint static setting, or unlimited when no
bound exists. Monitor definitions and enforcement rather than assuming an
invalid policy row fails closed. CREATE OR REPLACE RATE LIMIT is not
supported.
Static and dynamic precedence
The effective bound is deterministic:
- A user absent from every replica for a dimension uses the endpoint static setting unchanged.
- A covered user takes the tightest non-zero allowance across covering rate limits.
- If every covering limit returns
0, that explicit unlimited verdict overrides the endpoint static value. - A non-zero dynamic allowance and a non-zero static value compose tightest-wins. Ties are attributed to the rate limit.
Live updates do not require new DDL. Tightening concurrent_subscriptions
terminates newest subscriptions first so the oldest admitted seats remain.
Rows/bytes changes apply at the next sampling window; execution changes apply
at the next admission. Dropping a rate limit reverts covered users to the
remaining limits or the static endpoint setting.
SHOW RATE LIMITS;
DROP RATE LIMIT trader_subscriptions;SHOW RATE LIMITS returns name, dimension, source, uuid, version. Definitions
and drops are journaled; after restart, their internal system-class streams are
restored. The enforcement log itself is intentionally volatile.
Introspection and operational queries
SHOW ENDPOINTS;
SHOW AUTH PROVIDERS;
SHOW GRANTS;
SHOW RATE LIMITS;
SELECT name, params, claims, provider, limits,
subscribers, executions_total, bytes_out_total,
uuid, version
FROM system.endpoints
ORDER BY name;
SELECT ts_ms, event, user, role, relation, dimension,
allowed, observed, source, event_id
FROM system.governance_log
ORDER BY ts_ms DESC
LIMIT 200;SHOW ENDPOINTS contains definition cells only:
name, params, claims, provider, limits, digest, uuid, version.
system.endpoints adds the live counters between the definition and identity
cells.
system.governance_log records endpoint denials and kills, auth revocations,
operator login outcomes, and user-management events. It is capacity-bounded,
not journaled, and empty after restart. Export it if the incident record must be
durable.
Safe dependency teardown
Use dependency order rather than forcing catalog removal:
- stop or drain application clients;
- verify
system.endpoints.subscribersis zero and terminate any remaining transport sessions; - drop or replace endpoints that reference the provider only after the old subscriptions are gone, because catalog mutation does not revoke them and a drop removes their governor/metering state;
- revoke application grants that should no longer exist;
- drop rate limits no longer needed;
- drop the auth provider; and
- remove the underlying session or policy tables only after their internal subscriptions are gone.
For transport framing and reconnect behavior, continue with Browser and platform clients. For database users and the operator plane, see Operator governance.