Skip to content

This feature is currently in Preview.

Set up model deployment

This guide prepares a Snowflake account for deploying PyRel models. Pick the path that fits your needs — each builds on the previous one:

  1. Try it out — the fastest way to deploy a single model, using direct grants in one script. Great for evaluating; not meant for governed, multi-model production use.
  2. Governed setup — the recommended production setup: a shared admin database, admin/deploy roles, and least-privilege per-model roles, with reusable stored procedures for consistent data governance and model management.
  3. PAT authentication — optional, and required for direct Iceberg reads or the prescriptive reasoner. Adds token-based identity so scheduled refreshes run under the deploying user’s Snowflake identity. Builds on the governed setup.

Deployed PyRel models are fully embedded in Snowflake: model outputs become tables or views in the target schema, and model refresh is orchestrated via periodic tasks. Outputs can be governed like any other Snowflake data source — most commonly by granting read access to consumer roles in your organization.

Access to source tables and model execution are tied to the role assigned to each model during deployment. Any existing role in your account can be used for deployment, as long as it has privileges to manage objects in the target schema and to execute the model refresh tasks. Developers and system users that need to trigger a deployment must also be granted this role.

The quickest way to stand up a single model — direct grants only, no stored procedures, and no config table. Everything a single model needs is set up in one script. The default model_db is RAI_MODEL_SHARED, but you can use any existing database — no shared infrastructure is created.

The script does three things:

  1. One-time account setup — creates a shared deploy role (RAI_DEPLOYED_MODEL) that holds the privileges every model needs: warehouse usage and task execution. Reuse it across models.
  2. Per-model role setup — creates the model role, grants it the shared deploy role and the RAI Native App role, and grants the model role to the developer (and to the admin running the script).
  3. Model schema — ensures the target database and schema exist and are owned by the model role.

Set the configuration values at the top of the script — rai_deploy_role, model_role, model_developer, model_db, model_schema, and rai_app_name — then run the whole script as ACCOUNTADMIN:

-- =====================================================================================
-- RAI Model Deploy — direct grants (no stored procedures, no config table).
-- Run as ACCOUNTADMIN.
-- =====================================================================================
USE ROLE ACCOUNTADMIN;
-- ---- Configuration — adjust these values ----
-- Shared deployment role inherited by all model roles. One per account; reuse it across models.
SET rai_deploy_role = 'RAI_DEPLOYED_MODEL';
SET rai_warehouse = CURRENT_WAREHOUSE();
-- Role for this model. Bring your own if you already have a role with the right database and schema access;
-- otherwise a new role is created below.
SET model_role = 'MODEL_A_MANAGER_ROLE';
-- Snowflake user that will deploy the model (exact, case-sensitive).
SET model_developer = 'MY_USER';
-- Database that holds the model schema. Bring your own existing database — a new one is created only as a fallback.
SET model_db = 'RAI_MODEL_SHARED';
-- Schema where the model and all its data live. Bring your own if it already exists;
-- otherwise it is created below under the model role.
SET model_schema = 'MODEL_A_SCHEMA';
-- RAI Native App name; override only if the app is installed under another name.
SET rai_app_name = 'RELATIONALAI';
-- ============================================================
-- One-time account setup
-- ============================================================
CREATE ROLE IF NOT EXISTS IDENTIFIER($rai_deploy_role);
GRANT USAGE ON WAREHOUSE IDENTIFIER($rai_warehouse) TO ROLE IDENTIFIER($rai_deploy_role);
GRANT EXECUTE TASK ON ACCOUNT TO ROLE IDENTIFIER($rai_deploy_role);
-- Optional refresh observability. These are ACCOUNT-level privileges, so every model role that
-- inherits the deploy role could change the account-wide log/trace level. Refreshes still run
-- without them — they just emit no logs/traces unless LOG_LEVEL/TRACE_LEVEL is set at the account,
-- database, or schema. Uncomment to enable:
-- GRANT MODIFY LOG LEVEL ON ACCOUNT TO ROLE IDENTIFIER($rai_deploy_role);
-- GRANT MODIFY TRACE LEVEL ON ACCOUNT TO ROLE IDENTIFIER($rai_deploy_role);
-- ============================================================
-- Per-model role setup
-- ============================================================
CREATE ROLE IF NOT EXISTS IDENTIFIER($model_role);
-- Model role inherits shared deploy privileges (EXECUTE TASK, etc.)
GRANT ROLE IDENTIFIER($rai_deploy_role) TO ROLE IDENTIFIER($model_role);
-- Grant the RAI Native App role so the model's refreshes can call the app. Without it, refreshes
-- that call the app fail with: Unknown user-defined function <app>.API.USE_INDEX.
BEGIN
EXECUTE IMMEDIATE 'GRANT APPLICATION ROLE ' || $rai_app_name || '.RAI_USER TO ROLE ' || $model_role;
END;
-- Grant the model role to the developer, and to the admin running this script — the CREATE SCHEMA
-- below switches into the model role, which fails ("Requested role ... is not assigned to the
-- executing user") when the admin and developer are different people.
-- (Scripting block required: IDENTIFIER() doesn't support TO USER.)
BEGIN
EXECUTE IMMEDIATE 'GRANT ROLE ' || $model_role || ' TO USER "' || $model_developer || '"';
EXECUTE IMMEDIATE 'GRANT ROLE ' || $model_role || ' TO USER "' || CURRENT_USER() || '"';
END;
-- ============================================================
-- Model schema
-- ============================================================
SET model_schema_fqn = $model_db || '.' || $model_schema;
CREATE DATABASE IF NOT EXISTS IDENTIFIER($model_db); -- ideally an existing database; created here only as a fallback
GRANT USAGE ON DATABASE IDENTIFIER($model_db) TO ROLE IDENTIFIER($model_role);
GRANT CREATE SCHEMA ON DATABASE IDENTIFIER($model_db) TO ROLE IDENTIFIER($model_role); -- also lets the deploy create the sibling <schema>_META schema
-- If the schema does not exist yet, let the model role create it. The role then OWNS
-- the schema and automatically has every privilege on it — no further grants needed.
USE ROLE IDENTIFIER($model_role);
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($model_schema_fqn);
-- If you BROUGHT an existing schema owned by another role, the CREATE above was a silent
-- no-op and the model role owns nothing in it. Grant the object-creation privileges a
-- deploy needs. (Redundant but harmless when the model role already owns the schema.)
-- A deploy emits tables, views, dynamic tables, tasks, and procedures, tagged
-- relationalai_managed = 'true'.
USE ROLE ACCOUNTADMIN;
GRANT USAGE ON SCHEMA IDENTIFIER($model_schema_fqn) TO ROLE IDENTIFIER($model_role);
GRANT CREATE TABLE,
CREATE DYNAMIC TABLE,
CREATE VIEW,
CREATE TASK,
CREATE PROCEDURE,
CREATE TAG
ON SCHEMA IDENTIFIER($model_schema_fqn) TO ROLE IDENTIFIER($model_role);

The values you set above map into your model’s raiconfig.yaml — the model role becomes both the connection role and deployment.role, and <model_db>.<model_schema> becomes deployment.schema:

connections:
snowflake:
role: MODEL_A_MANAGER_ROLE # the model role the script created
# plus account, warehouse, rai_app_name, user, authenticator — see Deploy a model
deployment:
role: MODEL_A_MANAGER_ROLE # same model role
schema: RAI_MODEL_SHARED.MODEL_A_SCHEMA # the <model_db>.<model_schema> the script created

See Deploy a model for the full connection setup and the deploy command.

Section titled “Governed setup (recommended for production)”

The recommended way to organize Snowflake resources and access for governed model deployment: a shared admin database, an admin role and a deploy role, and a least-privilege per-model role that can access only the data its model reasons over — plus reusable stored procedures so you can repeat the per-model setup consistently. This gives you clean, auditable control over what each deployed model can read and write.

Run once per Snowflake account. It installs two reusable stored procedures in RAI_MODEL_SHARED.PUBLIC — RAI_ADMIN_SETUP and RAI_MODEL_ROLE_SETUP — then runs RAI_ADMIN_SETUP to create the shared infrastructure that all model deployments in the account will use:

  • Shared system database and schema (RAI_SYSTEM_SCHEMA) — the central home for RAI deployment resources.

  • Config table (RAI_DEPLOY_CONFIG) — stores shared settings so subsequent setup and deployment scripts can read configuration from one place.

  • Admin role (RAI_ADMIN) — manages the shared config table. ACCOUNTADMIN keeps ownership of the shared database and schema.

  • Shared deploy role (RAI_DEPLOYED_MODEL) — the operating role for model deployments. Granted the required privileges: access to the shared database and schema, warehouse usage, and task execution for scheduled model refresh. Each per-model role inherits these by being granted the shared role.

RAI_ADMIN_SETUP is idempotent and safe to re-run: it preserves the config table’s existing rows, including any PAT keys the PAT admin setup already wrote. RAI_MODEL_ROLE_SETUP is used in the next section, once per model.

USE ROLE ACCOUNTADMIN;
-- =====================================================================================
-- Bootstrap the shared database.
-- =====================================================================================
-- To use a different name, replace RAI_MODEL_SHARED everywhere it appears in this script — this SET,
-- both CREATE PROCEDURE qualified names, and the hard-coded constants inside the procedure bodies
-- (here and in the PAT procedures below). Easiest to keep the default unless you have a conflict.
SET rai_model_shared_db = 'RAI_MODEL_SHARED';
CREATE DATABASE IF NOT EXISTS IDENTIFIER($rai_model_shared_db);
GRANT OWNERSHIP ON DATABASE IDENTIFIER($rai_model_shared_db)
TO ROLE ACCOUNTADMIN COPY CURRENT GRANTS;
-- =====================================================================================
-- Create the RAI_ADMIN_SETUP procedure.
-- =====================================================================================
CREATE OR REPLACE PROCEDURE RAI_MODEL_SHARED.PUBLIC.RAI_ADMIN_SETUP(
RAI_ADMIN_ROLE VARCHAR, -- e.g. 'RAI_ADMIN'
RAI_DEPLOY_ROLE VARCHAR, -- e.g. 'RAI_DEPLOYED_MODEL'
RAI_WAREHOUSE VARCHAR -- warehouse to use
)
RETURNS VARCHAR
LANGUAGE SQL
EXECUTE AS CALLER -- REQUIRED: body switches role; caller must be ACCOUNTADMIN
AS
DECLARE
rai_model_shared_db VARCHAR DEFAULT 'RAI_MODEL_SHARED';
empty_arg EXCEPTION (-20001, 'RAI_ADMIN_ROLE, RAI_DEPLOY_ROLE, and RAI_WAREHOUSE are all required.');
system_schema VARCHAR;
config_table VARCHAR;
BEGIN
-- ---- Validation: fail fast on missing input ----
IF (RAI_ADMIN_ROLE IS NULL OR RAI_ADMIN_ROLE = ''
OR RAI_DEPLOY_ROLE IS NULL OR RAI_DEPLOY_ROLE = ''
OR RAI_WAREHOUSE IS NULL OR RAI_WAREHOUSE = '') THEN
RAISE empty_arg;
END IF;
system_schema := rai_model_shared_db || '.RAI_SYSTEM_SCHEMA';
config_table := system_schema || '.RAI_DEPLOY_CONFIG';
EXECUTE IMMEDIATE 'USE ROLE ACCOUNTADMIN'; -- guard; the only USE statement
-- Roles
EXECUTE IMMEDIATE 'CREATE ROLE IF NOT EXISTS ' || RAI_ADMIN_ROLE;
EXECUTE IMMEDIATE 'CREATE ROLE IF NOT EXISTS ' || RAI_DEPLOY_ROLE;
-- Warehouse access for both roles (idempotent: re-granting is a no-op)
EXECUTE IMMEDIATE 'GRANT USAGE ON WAREHOUSE ' || RAI_WAREHOUSE || ' TO ROLE ' || RAI_ADMIN_ROLE;
EXECUTE IMMEDIATE 'GRANT USAGE ON WAREHOUSE ' || RAI_WAREHOUSE || ' TO ROLE ' || RAI_DEPLOY_ROLE;
-- Database (reclaim ownership in case it drifted; no-op when already owned)
EXECUTE IMMEDIATE 'CREATE DATABASE IF NOT EXISTS ' || rai_model_shared_db;
EXECUTE IMMEDIATE 'GRANT OWNERSHIP ON DATABASE ' || rai_model_shared_db || ' TO ROLE ACCOUNTADMIN COPY CURRENT GRANTS';
-- Schema (reclaim ownership so this role can create and manage the config table below)
EXECUTE IMMEDIATE 'CREATE SCHEMA IF NOT EXISTS ' || system_schema;
EXECUTE IMMEDIATE 'GRANT OWNERSHIP ON SCHEMA ' || system_schema || ' TO ROLE ACCOUNTADMIN COPY CURRENT GRANTS';
-- Config table: create it only if absent so a re-run preserves existing rows — including the PAT
-- keys RAI_PAT_ADMIN_SETUP writes. Reclaim ownership first in case a drifted copy is owned by
-- another role.
BEGIN
EXECUTE IMMEDIATE 'GRANT OWNERSHIP ON TABLE ' || config_table || ' TO ROLE ACCOUNTADMIN COPY CURRENT GRANTS';
EXCEPTION
WHEN OTHER THEN NULL; -- table not present yet; the CREATE below makes it
END;
EXECUTE IMMEDIATE 'CREATE TABLE IF NOT EXISTS ' || config_table || ' (config_key VARCHAR, config_value VARCHAR)';
-- Upsert just these two keys (DELETE + INSERT), leaving any PAT keys intact. Duplicate rows would
-- also break the scalar config lookups elsewhere.
EXECUTE IMMEDIATE 'DELETE FROM ' || config_table || ' WHERE config_key IN (''rai_deploy_role'', ''rai_model_shared_db'')';
EXECUTE IMMEDIATE 'INSERT INTO ' || config_table || ' (config_key, config_value) VALUES '
|| '(''rai_deploy_role'', ?), (''rai_model_shared_db'', ?)'
USING (RAI_DEPLOY_ROLE, rai_model_shared_db);
-- Admin role access to the shared schema (no ownership transfer)
EXECUTE IMMEDIATE 'GRANT USAGE, CREATE SCHEMA ON DATABASE ' || rai_model_shared_db || ' TO ROLE ' || RAI_ADMIN_ROLE;
EXECUTE IMMEDIATE 'GRANT USAGE ON SCHEMA ' || system_schema || ' TO ROLE ' || RAI_ADMIN_ROLE;
EXECUTE IMMEDIATE 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ' || config_table || ' TO ROLE ' || RAI_ADMIN_ROLE;
-- Deploy role grants
EXECUTE IMMEDIATE 'GRANT USAGE ON DATABASE ' || rai_model_shared_db || ' TO ROLE ' || RAI_DEPLOY_ROLE || ' WITH GRANT OPTION';
EXECUTE IMMEDIATE 'GRANT USAGE ON SCHEMA ' || system_schema || ' TO ROLE ' || RAI_DEPLOY_ROLE || ' WITH GRANT OPTION';
EXECUTE IMMEDIATE 'GRANT CREATE SCHEMA ON DATABASE ' || rai_model_shared_db || ' TO ROLE ' || RAI_DEPLOY_ROLE;
EXECUTE IMMEDIATE 'GRANT SELECT ON TABLE ' || config_table || ' TO ROLE ' || RAI_DEPLOY_ROLE || ' WITH GRANT OPTION';
EXECUTE IMMEDIATE 'GRANT EXECUTE TASK ON ACCOUNT TO ROLE ' || RAI_DEPLOY_ROLE;
-- Optional refresh observability (opt-in). MODIFY LOG/TRACE LEVEL are ACCOUNT-level privileges,
-- so every model role inheriting the deploy role could change the account-wide log/trace level.
-- Refreshes still run without them — they just emit no logs/traces unless LOG_LEVEL/TRACE_LEVEL
-- is set at the account, database, or schema. Uncomment to enable:
-- EXECUTE IMMEDIATE 'GRANT MODIFY LOG LEVEL ON ACCOUNT TO ROLE ' || RAI_DEPLOY_ROLE;
-- EXECUTE IMMEDIATE 'GRANT MODIFY TRACE LEVEL ON ACCOUNT TO ROLE ' || RAI_DEPLOY_ROLE;
RETURN 'Setup complete.';
END;
-- =====================================================================================
-- Create the RAI_MODEL_ROLE_SETUP procedure.
-- =====================================================================================
CREATE OR REPLACE PROCEDURE RAI_MODEL_SHARED.PUBLIC.RAI_MODEL_ROLE_SETUP(
MODEL_ROLE VARCHAR, -- role with source table access to use for this model
MODEL_DEVELOPER VARCHAR, -- user to deploy the model (exact, case-sensitive)
RAI_DEPLOY_ROLE VARCHAR, -- shared deploy role, e.g. 'RAI_DEPLOYED_MODEL'
RAI_APP_NAME VARCHAR DEFAULT 'RELATIONALAI' -- RAI Native App; override only if installed under another name
)
RETURNS VARCHAR
LANGUAGE SQL
EXECUTE AS CALLER -- caller must be ACCOUNTADMIN (needs MANAGE GRANTS on the deploy role)
AS
DECLARE
empty_arg EXCEPTION (-20001, 'MODEL_ROLE, MODEL_DEVELOPER, and RAI_DEPLOY_ROLE are all required.');
app_role VARCHAR;
app_grant_status VARCHAR;
BEGIN
-- ---- Validation: fail fast on missing input ----
IF (MODEL_ROLE IS NULL OR MODEL_ROLE = ''
OR MODEL_DEVELOPER IS NULL OR MODEL_DEVELOPER = ''
OR RAI_DEPLOY_ROLE IS NULL OR RAI_DEPLOY_ROLE = '') THEN
RAISE empty_arg;
END IF;
-- Fall back to the default if the app name is passed in empty.
app_role := COALESCE(NULLIF(RAI_APP_NAME, ''), 'RELATIONALAI') || '.RAI_USER';
EXECUTE IMMEDIATE 'USE ROLE ACCOUNTADMIN'; -- ensure we operate as ACCOUNTADMIN
-- Grant the shared deploy role to the model role.
EXECUTE IMMEDIATE 'GRANT ROLE ' || RAI_DEPLOY_ROLE || ' TO ROLE ' || MODEL_ROLE;
-- Grant the RAI Native App role (<app>.RAI_USER) to the model role so it can use the app.
BEGIN
EXECUTE IMMEDIATE 'GRANT APPLICATION ROLE ' || app_role || ' TO ROLE ' || MODEL_ROLE;
app_grant_status := 'Granted ' || app_role || ' to ' || MODEL_ROLE || '.';
EXCEPTION
WHEN OTHER THEN
app_grant_status := 'WARNING: could not grant ' || app_role || ' to '
|| MODEL_ROLE || ' — app not installed? ' || SQLERRM;
END;
-- Grant the model role to the developer.
EXECUTE IMMEDIATE 'GRANT ROLE ' || MODEL_ROLE || ' TO USER "' || MODEL_DEVELOPER || '"';
RETURN 'Model role setup complete. ' || app_grant_status;
END;
-- =====================================================================================
-- Run the one-time account setup.
-- =====================================================================================
-- Adjust the role/warehouse names as needed.
SET rai_admin_role = 'RAI_ADMIN';
SET rai_deploy_role = 'RAI_DEPLOYED_MODEL';
SET rai_warehouse = CURRENT_WAREHOUSE();
CALL RAI_MODEL_SHARED.PUBLIC.RAI_ADMIN_SETUP(
$rai_admin_role, $rai_deploy_role, $rai_warehouse);

Every PyRel model should be assigned a role that provides the narrowest access to the specific data domain that the model is meant to reason over. Each model also usually has one or more developers working on it, along with e.g. a system user for CI. Every Snowflake user that should be able to deploy the model must be granted the selected role as well.

This is completely separate from governing the outputs of a model (the tables and views it creates in the target schema). To govern outputs, grant appropriate read privileges on the target schema to all consuming roles.

Select the role to use for deployment and a user that should be able to trigger the deployment, and call the RAI_MODEL_ROLE_SETUP procedure created in the previous section:

USE ROLE ACCOUNTADMIN;
CALL RAI_MODEL_SHARED.PUBLIC.RAI_MODEL_ROLE_SETUP(
'<MODEL_ROLE>', -- select a role with access to the source data for this model
'<USERNAME>', -- exact, case-sensitive Snowflake username
'<DEPLOY_ROLE>', -- the shared deploy role created during the admin setup (default: 'RAI_DEPLOYED_MODEL')
'<RAI_APP_NAME>' -- RAI Native App name; override if installed under another name
);

Use an existing deployment-ready model project or create one. In your active raiconfig.yaml profile, fill in the connection fields for your Snowflake account and authentication method. You will also need to configure which warehouse to use for SQL execution. For example:

default_connection: sf
connections:
sf:
type: snowflake
account: my_org-my_account
warehouse: my_warehouse
rai_app_name: relationalai # name of the RAI app in your Snowflake account
authenticator: externalbrowser # how the CLI signs in to Snowflake (options below)
user: you@example.com

authenticator selects how the CLI signs in to Snowflake. See Configure Snowflake authentication for the full setup of each option:

Optional, and required for direct Iceberg reads or the prescriptive reasoner. PAT (Programmatic Access Token) authentication makes an RAI model’s scheduled refreshes authenticate to Snowflake under the deploying user’s identity, rather than a single shared app identity. Each model gets its own PAT restricted to its dedicated role, so the reasoners can access — and only access — the data and resources that role permits.

  • Complete the Governed setup first. PAT builds on it: the admin script below reads the shared config table (RAI_DEPLOY_CONFIG) to find the deploy role, and grants the PAT privileges onto that same role. (PAT is not available on the Try it out path.)

Snowflake-managed Iceberg (CATALOG = 'SNOWFLAKE') writes to Snowflake-owned sfc-* buckets that are not contractually stable; for a pinnable host, prefer a customer-managed external volume (your own bucket), or use a broad egress rule for testing only. A *.amazonaws.com wildcard does not cover this host: Snowflake network-rule wildcards match a single subdomain level.

  1. Bind the iceberg_eai reference to that EAI so the app can attach it to the engine. This is a one-time call per app install, not per model:
    CALL <APP>.app.update_reference('iceberg_eai','SET',
    SYSTEM$REFERENCE('EXTERNAL ACCESS INTEGRATION','<RAI_EAI>','PERSISTENT','USAGE'));
    This grants the app USAGE on the EAI and lets the deploy attach reference('iceberg_eai') to the engine automatically. Verify the reference is bound:
    SHOW REFERENCES IN APPLICATION <APP>;
    -- Confirm the ICEBERG_EAI row shows your EAI name in the bound-object column.

No Iceberg source table yet? To try it, create a Snowflake-managed Iceberg table and grant the model role SELECT. Managed-storage accounts use CATALOG = 'SNOWFLAKE' with no external volume; otherwise add EXTERNAL_VOLUME + BASE_LOCATION (check with SHOW EXTERNAL VOLUMES;):

CREATE OR REPLACE ICEBERG TABLE <SRC_DB>.<SRC_SCHEMA>.CONNECTIONS (
station_1 INT,
station_2 INT
)
CATALOG = 'SNOWFLAKE';
INSERT INTO <SRC_DB>.<SRC_SCHEMA>.CONNECTIONS VALUES (1,2),(1,3),(3,4),(4,5);
GRANT USAGE ON DATABASE <SRC_DB> TO ROLE <MODEL_ROLE>;
GRANT USAGE ON SCHEMA <SRC_DB>.<SRC_SCHEMA> TO ROLE <MODEL_ROLE>;
GRANT SELECT ON TABLE <SRC_DB>.<SRC_SCHEMA>.CONNECTIONS TO ROLE <MODEL_ROLE>;
-- Confirm Snowflake reports it as Iceberg (the engine reads it under the PAT only if so):
-- SHOW TABLES LIKE 'CONNECTIONS' IN SCHEMA <SRC_DB>.<SRC_SCHEMA>; -- is_iceberg = true

:::

Run once by a Snowflake account administrator. Creates the External Access Integration, network rule, and network policy that allow model tasks to authenticate with the RAI engine via PAT.

-- ================================================================
-- PAT infrastructure names below can be kept as defaults or
-- customized to avoid naming conflicts. If your admin used a
-- different shared database, replace the hard-coded RAI_MODEL_SHARED
-- references in the procedure bodies below to match.
-- ================================================================
SET rai_warehouse = current_warehouse();
SET rai_eai_name = 'RAI_EAI';
SET rai_eai_network_rule = 'RAI_NETWORK_RULE';
SET rai_pat_block_policy_name = 'RAI_PAT_BLOCK_POLICY';
SET rai_pat_name = 'RAI_PAT';
USE ROLE ACCOUNTADMIN;
USE WAREHOUSE IDENTIFIER($rai_warehouse);
-- =====================================================================================
-- Create the RAI_PAT_ADMIN_SETUP procedure.
-- =====================================================================================
CREATE OR REPLACE PROCEDURE RAI_MODEL_SHARED.PUBLIC.RAI_PAT_ADMIN_SETUP(
RAI_EAI_NAME VARCHAR DEFAULT 'RAI_EAI',
RAI_EAI_NETWORK_RULE VARCHAR DEFAULT 'RAI_NETWORK_RULE',
RAI_PAT_BLOCK_POLICY_NAME VARCHAR DEFAULT 'RAI_PAT_BLOCK_POLICY',
RAI_PAT_NAME VARCHAR DEFAULT 'RAI_PAT'
)
RETURNS VARCHAR
LANGUAGE SQL
EXECUTE AS CALLER -- caller must be ACCOUNTADMIN (needs CREATE INTEGRATION and MANAGE GRANTS)
AS
DECLARE
config_missing EXCEPTION (-20101, 'RAI deploy config not found — run the governed One-time account setup (RAI_ADMIN_SETUP) first.');
rai_config_table VARCHAR DEFAULT 'RAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA.RAI_DEPLOY_CONFIG';
rai_system_schema VARCHAR DEFAULT 'RAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA';
rai_network_rule_fq VARCHAR;
rai_deploy_role VARCHAR DEFAULT NULL;
BEGIN
LET rs RESULTSET := (EXECUTE IMMEDIATE 'SELECT config_value FROM ' || :rai_config_table || ' WHERE config_key = ''rai_deploy_role''');
LET c CURSOR FOR rs;
OPEN c; FETCH c INTO :rai_deploy_role; CLOSE c;
IF (:rai_deploy_role IS NULL) THEN
RAISE config_missing;
END IF;
rai_network_rule_fq := :rai_system_schema || '.' || RAI_EAI_NETWORK_RULE;
-- Store PAT config values (DELETE first to stay idempotent — duplicate rows break scalar lookups).
EXECUTE IMMEDIATE 'DELETE FROM ' || :rai_config_table
|| ' WHERE config_key IN (''rai_eai_name'', ''rai_eai_network_rule'', ''rai_pat_block_policy_name'', ''rai_pat_name'')';
EXECUTE IMMEDIATE 'INSERT INTO ' || :rai_config_table || ' (config_key, config_value) VALUES'
|| ' (''rai_eai_name'', ''' || RAI_EAI_NAME || ''')'
|| ',(''rai_eai_network_rule'', ''' || RAI_EAI_NETWORK_RULE || ''')'
|| ',(''rai_pat_block_policy_name'', ''' || RAI_PAT_BLOCK_POLICY_NAME || ''')'
|| ',(''rai_pat_name'', ''' || RAI_PAT_NAME || ''')';
EXECUTE IMMEDIATE 'GRANT CREATE NETWORK RULE ON SCHEMA ' || :rai_system_schema || ' TO ROLE ACCOUNTADMIN';
-- IF NOT EXISTS: re-running must not reset the rule's VALUE_LIST (populated with RAI endpoints later).
EXECUTE IMMEDIATE 'CREATE NETWORK RULE IF NOT EXISTS ' || :rai_network_rule_fq
|| ' TYPE = HOST_PORT MODE = EGRESS VALUE_LIST = () COMMENT = ''Network Rule for RAI App''';
EXECUTE IMMEDIATE 'CREATE EXTERNAL ACCESS INTEGRATION IF NOT EXISTS ' || RAI_EAI_NAME
|| ' ALLOWED_NETWORK_RULES = (' || :rai_network_rule_fq || ')'
|| ' ALLOWED_AUTHENTICATION_SECRETS = ALL ENABLED = TRUE';
-- Network policy for PAT authentication — attach per user in the Deployment User Setup below.
EXECUTE IMMEDIATE 'CREATE NETWORK POLICY IF NOT EXISTS ' || RAI_PAT_BLOCK_POLICY_NAME
|| ' BLOCKED_IP_LIST = (''0.0.0.0/0'')';
EXECUTE IMMEDIATE 'GRANT USAGE ON INTEGRATION ' || RAI_EAI_NAME
|| ' TO ROLE ' || :rai_deploy_role;
EXECUTE IMMEDIATE 'GRANT CREATE SECRET ON SCHEMA ' || :rai_system_schema
|| ' TO ROLE ' || :rai_deploy_role || ' WITH GRANT OPTION';
EXECUTE IMMEDIATE 'GRANT USAGE ON PROCEDURE RAI_MODEL_SHARED.PUBLIC.RAI_PAT_SETUP(VARCHAR, VARCHAR, INTEGER)'
|| ' TO ROLE ' || :rai_deploy_role;
-- USAGE on the PUBLIC schema (where RAI_PAT_SETUP lives) so the model/deploy role — not only
-- ACCOUNTADMIN — can call it (the self-issued pattern, where a developer runs RAI_PAT_SETUP for
-- their own user). Without it the call fails with "Unknown user-defined function ... RAI_PAT_SETUP".
EXECUTE IMMEDIATE 'GRANT USAGE ON SCHEMA RAI_MODEL_SHARED.PUBLIC TO ROLE ' || :rai_deploy_role;
RETURN 'PAT admin setup complete. Network rule: ' || :rai_network_rule_fq || ', EAI: ' || RAI_EAI_NAME;
END;
-- =====================================================================================
-- Create the RAI_PAT_SETUP procedure.
-- (Defined here once by the admin; called later per model in the Developer Setup.)
-- =====================================================================================
CREATE OR REPLACE PROCEDURE RAI_MODEL_SHARED.PUBLIC.RAI_PAT_SETUP(
MODEL_ROLE VARCHAR, -- model manager role (PAT is restricted to this role)
USER_NAME VARCHAR, -- Snowflake user that will run the model (exact, case-sensitive)
DAYS_TO_EXPIRY INTEGER DEFAULT 90 -- expiry for a newly issued PAT; re-running rotates & extends it
)
RETURNS VARIANT
LANGUAGE SQL
EXECUTE AS CALLER
AS
DECLARE
config_missing EXCEPTION (-20102, 'RAI deploy config not found — run the PAT Admin Setup first.');
rai_config_table VARCHAR DEFAULT 'RAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA.RAI_DEPLOY_CONFIG';
rai_eai_name VARCHAR DEFAULT NULL;
rai_pat_name VARCHAR DEFAULT NULL;
model_pat_name VARCHAR;
pat_secret_fq VARCHAR;
token_name_val VARCHAR;
token_secret_val VARCHAR;
pat_result RESULTSET;
token_exists BOOLEAN DEFAULT FALSE; -- set from SHOW … PROGRAMMATIC ACCESS TOKENS below
rotate_grace_hours INTEGER DEFAULT 24; -- old token stays valid this long after a rotation
BEGIN
LET rs1 RESULTSET := (EXECUTE IMMEDIATE 'SELECT config_value FROM ' || :rai_config_table || ' WHERE config_key = ''rai_eai_name''');
LET c1 CURSOR FOR rs1;
OPEN c1; FETCH c1 INTO :rai_eai_name; CLOSE c1;
LET rs2 RESULTSET := (EXECUTE IMMEDIATE 'SELECT config_value FROM ' || :rai_config_table || ' WHERE config_key = ''rai_pat_name''');
LET c2 CURSOR FOR rs2;
OPEN c2; FETCH c2 INTO :rai_pat_name; CLOSE c2;
IF (:rai_eai_name IS NULL OR :rai_pat_name IS NULL) THEN
RAISE config_missing;
END IF;
-- Derive per-model PAT and secret names (role suffix keeps them unique across models).
-- UPPER() matches how Snowflake stores unquoted identifiers; without it a mixed-case MODEL_ROLE
-- causes the existence check below to miss an existing token and attempt ADD instead of ROTATE.
model_pat_name := UPPER(:rai_pat_name || '_' || MODEL_ROLE);
pat_secret_fq := 'RAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA.' || :model_pat_name || '_SECRET';
-- Create the token, or ROTATE it if one already exists. Decide by CHECKING whether the token
-- exists (deterministic) rather than relying on error handling. ROTATE keeps the previous token
-- valid for rotate_grace_hours, so a scheduled refresh keeps working while the secret is updated —
-- unlike REMOVE + ADD, which invalidates the old token immediately.
EXECUTE IMMEDIATE 'SHOW USER PROGRAMMATIC ACCESS TOKENS FOR USER "' || USER_NAME || '"';
SELECT COUNT(*) > 0 INTO :token_exists
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
WHERE "name" = :model_pat_name;
IF (token_exists) THEN
-- Rotate. Near expiry the grace window can exceed the token's remaining life and be rejected,
-- so fall back to an immediate rotation (old secret expires now; a fresh token is still issued).
BEGIN
pat_result := (EXECUTE IMMEDIATE
'ALTER USER "' || USER_NAME || '" ROTATE PROGRAMMATIC ACCESS TOKEN ' || :model_pat_name
|| ' EXPIRE_ROTATED_TOKEN_AFTER_HOURS = ' || :rotate_grace_hours::STRING);
EXCEPTION
WHEN OTHER THEN
pat_result := (EXECUTE IMMEDIATE
'ALTER USER "' || USER_NAME || '" ROTATE PROGRAMMATIC ACCESS TOKEN ' || :model_pat_name
|| ' EXPIRE_ROTATED_TOKEN_AFTER_HOURS = 0');
END;
ELSE
pat_result := (EXECUTE IMMEDIATE
'ALTER USER "' || USER_NAME || '" ADD PROGRAMMATIC ACCESS TOKEN ' || :model_pat_name
|| ' ROLE_RESTRICTION = ' || MODEL_ROLE
|| ' DAYS_TO_EXPIRY = ' || DAYS_TO_EXPIRY::STRING);
END IF;
LET c3 CURSOR FOR pat_result;
OPEN c3;
FETCH c3 INTO :token_name_val, :token_secret_val;
CLOSE c3;
-- CREATE OR REPLACE, not CREATE IF NOT EXISTS + ALTER: model roles can create secrets in this
-- schema and the names are predictable, so another model role could pre-create this secret and
-- keep READ on it. Replacing drops every existing grant, so only the grants below remain.
-- A deploy role granted READ separately must be re-granted after each rotation.
EXECUTE IMMEDIATE 'CREATE OR REPLACE SECRET ' || :pat_secret_fq
|| ' TYPE = GENERIC_STRING SECRET_STRING = ''' || :token_secret_val || '''';
-- USAGE lets the model role reference the secret; READ lets the deploy create the UDF that reads
-- the token (get_generic_secret_string). Both go to the model role so the deploy can read the
-- token even when the secret is owned by a different role (e.g. ACCOUNTADMIN ran this). Without
-- READ the deploy fails with: Secret '<name>' does not exist or operation not authorized.
EXECUTE IMMEDIATE 'GRANT USAGE ON SECRET ' || :pat_secret_fq
|| ' TO ROLE ' || MODEL_ROLE;
EXECUTE IMMEDIATE 'GRANT READ ON SECRET ' || :pat_secret_fq
|| ' TO ROLE ' || MODEL_ROLE;
RETURN OBJECT_CONSTRUCT(
'deploy_role', MODEL_ROLE,
'pat_secret', :pat_secret_fq,
'pat_eai', :rai_eai_name
);
END;
-- =====================================================================================
-- Run the PAT admin setup. Adjust arguments if you changed the names above.
-- =====================================================================================
CALL RAI_MODEL_SHARED.PUBLIC.RAI_PAT_ADMIN_SETUP(
$rai_eai_name,
$rai_eai_network_rule,
$rai_pat_block_policy_name,
$rai_pat_name
);
  • The Admin setup above must be completed first (it creates the network policy).

Run once per deployment user by a Snowflake account administrator.

Snowflake requires a network policy on any user that authenticates with a PAT. The admin setup created a block-all policy (BLOCKED_IP_LIST = ('0.0.0.0/0')) that blocks the user from connecting from any IP address. For prescriptive models, Step 2 then allows the one origin the solver needs — the prescriptive solver compute pools — so the solver’s PAT logins work from there. Logic and Iceberg models don’t currently require this compute-pool rule.

For production deployments, we recommend a dedicated user account created with the policy attached:

-- ================================================================
-- Set your values here. Run as ACCOUNTADMIN, in the same session as
-- the Admin setup above (it sets $rai_pat_block_policy_name).
-- ================================================================
SET model_exec_user = 'RAI_MODEL_EXEC_USER'; -- dedicated user for model deployment/tasks
USE ROLE ACCOUNTADMIN;
SET sql = 'CREATE USER IF NOT EXISTS "' || $model_exec_user || '"'
|| ' NETWORK_POLICY = ' || $rai_pat_block_policy_name
|| ' COMMENT = ''Dedicated user for RAI model deployment and task execution''';
EXECUTE IMMEDIATE $sql;

To use an existing user instead, attach the policy directly:

SET sql = 'ALTER USER "' || $model_exec_user || '" SET NETWORK_POLICY = ' || $rai_pat_block_policy_name;
EXECUTE IMMEDIATE $sql;

After creating the user, grant it the model manager role for each model it will run (see the Per-role setup) — a PAT can only be restricted to a role the user holds.

  • An admin has completed the Admin setup and Deployment user setup above.
  • The model manager role is already created (see Per-role setup); the deployment schema is created on first rai models deploy.
  • The deployment user has been granted the model manager role — a PAT can only be restricted to a role the user holds.

Run this once per model to create a Personal Access Token (PAT) and store it as a Snowflake secret. The PAT is specific to this model: it is restricted to the model manager role, so sessions authenticating with it run with that role as their primary role.

Choose whose identity the token represents — that decides who runs RAI_PAT_SETUP:

Call the RAI_PAT_SETUP procedure installed during the Admin setup. It issues the PAT — or rotates it if one already exists, keeping the old token valid for a grace window (EXPIRE_ROTATED_TOKEN_AFTER_HOURS, 24h by default) so a scheduled refresh keeps working while the secret updates — stores it as a Snowflake secret, grants the model role USAGE/READ on that secret, and returns the values your deployment needs:

-- Returns deploy_role, pat_secret, and pat_eai — use these values in raiconfig.yaml.
-- Self-issued: run as the model role. Dedicated service account: run as ACCOUNTADMIN.
CALL RAI_MODEL_SHARED.PUBLIC.RAI_PAT_SETUP(
'<MODEL_ROLE>', -- role the PAT is restricted to (has this model's data access)
'<USERNAME>' -- the token owner: the developer's own user, or the dedicated service account
);

RAI_PAT_SETUP returns deploy_role, pat_secret, and pat_eai. Set them under deployment in raiconfig.yaml — plus pat_user and pat_warehouse for prescriptive models:

raiconfig.yaml keySourceValue (default names)
deployment.role (also the connection role)returned deploy_roleyour model role
deployment.pat_secretreturned pat_secretRAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA.RAI_PAT_<MODEL_ROLE>_SECRET
deployment.pat_eaireturned pat_eaiRAI_EAI
deployment.pat_useryou supply (prescriptive only)the token owner (the deployment user)
deployment.pat_warehouseyou supply (prescriptive only)a warehouse the model role can use
deployment:
role: <MODEL_ROLE>
schema: RAI_MODEL_SHARED.<MODEL_SCHEMA>
pat_secret: RAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA.RAI_PAT_<MODEL_ROLE>_SECRET
pat_eai: RAI_EAI
# prescriptive only:
pat_user: <DEPLOYMENT_USER>
pat_warehouse: <WAREHOUSE>
  • pat_secret: Fully-qualified Snowflake secret holding this model’s PAT.
  • pat_eai: External Access Integration that allows reading the secret at runtime.
  • pat_user: The token’s owner (the deployment user). Required for the prescriptive reasoner.
  • pat_warehouse: A warehouse the model role can use for Snowflake reads and bulk loads. Required for the prescriptive reasoner.

Logic deployments that read Iceberg directly need only pat_secret and pat_eai; the prescriptive reasoner requires all four. Direct Iceberg source reads also need PyRel 1.36.0 or later and both reasoners.logic.direct_iceberg: true and reasoners.logic.lqp.compiler: direct, as described in PAT authentication.

See Deploy a model for the full deployment workflow, and Additional setup for the prescriptive reasoner for the prescriptive-only fields.

Additional setup for the prescriptive reasoner

Section titled “Additional setup for the prescriptive reasoner”

This section covers the additional configuration, network access, and permissions needed for the prescriptive reasoner to read inputs and write results in Snowflake on behalf of a PAT user during model refreshes. It builds on the PAT authentication setup above.

Deployment currently supports models using rules-based reasoning and, with PyRel 1.30.1 or later, prescriptive Problems. A deployed prescriptive model must declare its solve with problem.configure(...); problem.solve() fails under deployment. Configured Problems also require PAT authentication with all four deployment.pat_* fields. Deploy a model doesn’t cover prescriptive models yet. Use it for the general deploy workflow, and the section below for the additional PAT setup prescriptive models need.

  • Complete PAT authentication above first — this section is the additional setup the prescriptive reasoner needs on top of it.
  • The governed-setup roles must already exist (see Governed setup); the deployment schema is created on first rai models deploy.

For a prescriptive model with PAT authentication, complete these in order. Steps 1–3 are the generic setup shared by all reasoners; steps 4–8 are covered in this section.

  1. Model deployment infrastructure (admin, once per account) — roles, shared DB, task/warehouse grants. See Governed setup.
  2. PAT infrastructure (admin, once per account) — EAI, network policy, deployment-user policy. See Admin setup.
  3. PAT + secret (per model) — role-restricted token stored as a secret. See Developer setup.
  4. Configure all four PAT fields — step 1 below.
  5. Allow the reasoner’s compute pools through the network policy — step 2 below.
  6. PAT-role privileges and Native App caller grants — steps 3 and 4 below.
  7. Deploy and verify a refresh — step 5 below.
  8. Troubleshoot any refresh failure — Troubleshooting.

Use these placeholders consistently in the examples below. The Where it’s set · default column points to where each value comes from in the setup guides and its default when you use the RAI setup procedures (substitute your own names if you customized them):

PlaceholderMeaningWhere it’s set · default
<DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>The model’s deployment.schema, containing prescriptive inputs and outputs.Created by the first rai models deploy · default RAI_MODEL_SHARED.MODEL_A_SCHEMA
<PAT_DB>.<PAT_SCHEMA>The admin schema holding PAT infrastructure (network rule + secrets). It may differ from the deployment schema.Created by Admin Setup · default RAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA
<PAT_USER>The Snowflake user who owns the token stored in pat_secret (the <DEPLOYMENT_USER>).Deployment User Setup · default RAI_MODEL_EXEC_USER
<PAT_ROLE>The token’s ROLE_RESTRICTION — the model role the PAT is restricted to. Usually the same as the role that runs rai models deploy, but see the grants below if they differ.The MODEL_ROLE in Developer Setup · your model role, e.g. MODEL_A_MANAGER_ROLE (not the shared RAI_DEPLOYED_MODEL)
<PAT_WAREHOUSE>The warehouse the reasoner uses for Snowflake reads and writes; must be usable by <PAT_ROLE>.A warehouse granted to the deploy role · use your account’s warehouse
<PAT_NETWORK_POLICY>The network policy effective for the PAT user.Created by Admin Setup, attached in Deployment User Setup · default RAI_PAT_BLOCK_POLICY when attached to the PAT user (see note below)
<RAI_APP_NAME>The installed Native App used by this deployment, not an account role or an application package.Your connection’s rai_app_name · default RELATIONALAI

Have an administrator apply the network changes and grants. Creating a network rule requires CREATE NETWORK RULE on its schema; changing a policy requires ownership of that policy. Caller grants require the account-level MANAGE CALLER GRANTS privilege and access to the referenced objects. Ordinary role grants require the appropriate grant authority, such as MANAGE GRANTS.

Set all four fields under deployment in the active raiconfig.yaml profile:

deployment:
schema: <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>
pat_secret: RAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA.RAI_PAT_<MODEL_ROLE>_SECRET
pat_eai: RAI_EAI
pat_user: <PAT_USER>
pat_warehouse: <PAT_WAREHOUSE>
  • pat_secret must contain a valid PAT belonging to pat_user.
  • pat_eai must be enabled and allow access to that secret, as configured in the PAT authentication setup. It does not determine the identity represented by the token.
  • pat_user must identify the token’s owner, not simply the user running the CLI.
  • pat_warehouse must be accessible to the token’s restricted role. Access via another role assigned to the user is not enough: role-restricted PAT sessions do not use secondary roles.

pat_secret must use three unquoted Snowflake identifiers; pat_eai must be a single unquoted Snowflake identifier. The warehouse is for Snowflake queries and bulk loading; it is not the compute pool that runs the solver. It need not be the same warehouse used by the refresh task.

General PAT configuration permits the secret/EAI pair without user/warehouse, but configured Prescriptive Problems require all four fields. PyRel checks for missing fields, not whether the token owner, network policy, and Snowflake privileges are correct.

The deployment role also needs access to the PAT infrastructure. If an administrator created it, have them grant the following to <YOUR_ROLE>, the role used to deploy the model (which may differ from <PAT_ROLE>):

GRANT USAGE ON INTEGRATION RAI_EAI TO ROLE <YOUR_ROLE>;
GRANT USAGE ON DATABASE <PAT_DB> TO ROLE <YOUR_ROLE>;
GRANT USAGE ON SCHEMA <PAT_DB>.<PAT_SCHEMA> TO ROLE <YOUR_ROLE>;

Use the actual pat_eai name. The deployment role must also own the secret or have READ on it, as described in PAT authentication. Do not grant secret access to unrelated roles. RAI_PAT_SETUP recreates the secret on every rotation, which drops any grant except the model role’s, so re-apply a separately granted READ after each rotation.

2. Allow connections from the reasoner’s compute pools

Section titled “2. Allow connections from the reasoner’s compute pools”

Having a network policy attached to the PAT user is not sufficient. The policy must explicitly allow customer-credential connections originating from the compute pools used by the prescriptive reasoner.

Inspect the user’s policy and its existing rules before changing anything:

-- Use double quotes for SQL user identifiers such as "your.name@example.com".
SHOW PARAMETERS LIKE 'NETWORK_POLICY' FOR USER <PAT_USER>;
-- Also check the account policy if the user has no user-level assignment.
SHOW PARAMETERS LIKE 'NETWORK_POLICY' IN ACCOUNT;
DESC NETWORK POLICY <PAT_NETWORK_POLICY>;

A user-level policy takes precedence over the account-level policy. Update the policy effective for this user. If it is shared with other users, assess the effect on them before changing it. Preserve existing access restrictions and interactive access where required.

Create a separate ingress rule in the PAT infrastructure schema, listing the compute pools that host the prescriptive solver. These pools are named with your app’s prefix plus a _SOLVER suffix and the reasoner size — for example RELATIONAL_AI_HIGHMEM_X64_M_SOLVER for the default install, or <APP_PREFIX>_HIGHMEM_X64_M_SOLVER for an app installed under another name.

Get the exact names from SHOW COMPUTE POOLS — the prefix reflects the installed app and the sizes reflect your configured reasoner sizes. (These solver pools are distinct from the engine/query pools listed under compute pools, which do not carry the _SOLVER suffix.)

Mind the quoting — it differs between the two statements:

  • VALUE_LIST pool names are single-quoted strings.
  • ALLOWED_NETWORK_RULE_LIST takes a parenthesized list of network-rule identifiers. The parentheses are what matter: a bare quoted name without them (= 'db.schema.rule') fails with invalid type of property, while both = (db.schema.rule) and = ('db.schema.rule') work.
CREATE NETWORK RULE <PAT_DB>.<PAT_SCHEMA>.SPCS_PAT_POOLS
TYPE = COMPUTE_POOL
MODE = INGRESS
VALUE_LIST = (
'RELATIONAL_AI_HIGHMEM_X64_M_SOLVER', -- quoted; confirm exact names with SHOW COMPUTE POOLS
'RELATIONAL_AI_HIGHMEM_X64_S_SOLVER'
)
COMMENT = 'Allow prescriptive reasoners to connect using customer-provided credentials';
ALTER NETWORK POLICY <PAT_NETWORK_POLICY>
ADD ALLOWED_NETWORK_RULE_LIST = (<PAT_DB>.<PAT_SCHEMA>.SPCS_PAT_POOLS); -- unquoted identifier

If the rule already exists, inspect it with DESC NETWORK RULE and update its VALUE_LIST with ALTER NETWORK RULE as needed. Keep all required pools in the list. Avoid allowing ALL compute pools just to resolve a connection failure.

This COMPUTE_POOL / INGRESS rule belongs in the network policy, not in pat_eai. Keep the EAI’s existing HOST_PORT / EGRESS rule separate. A block-all policy from the initial PAT setup also needs this compute-pool allow rule before the reasoner can connect. Do not bypass network-policy enforcement or open public IP access as a workaround.

3. Verify the PAT role’s ordinary privileges

Section titled “3. Verify the PAT role’s ordinary privileges”

There are two independent permission checks:

  1. The PAT user’s restricted role must be allowed to perform the operation.
  2. The Native App’s caller grants must allow it to use that privilege on behalf of the user.

Caller grants do not grant privileges to the PAT role. If the PAT role already owns the objects and has the required access, duplicate ordinary grants are unnecessary.

First check who owns the deployment schema — rai models deploy creates the schema (and its objects) under the deploy role, so if that role is also the PAT role, it owns everything and these grants are unnecessary:

SHOW SCHEMAS LIKE '<DEPLOYMENT_SCHEMA>' IN DATABASE <DEPLOYMENT_DB>;

Read the owner column to decide the schema-object grants (everything except the warehouse):

  • Owner is <PAT_ROLE> and <PAT_ROLE> is also the role that runs rai models deploy → it creates and owns all objects in the schema. Skip the schema-object grants below (but not the warehouse grant above). If a different role runs the deploy, grant the object privileges even when <PAT_ROLE> owns the schema — schema ownership does not cover objects created by another role.
  • Schema not listed (not deployed yet) → the next rai models deploy will create it under the deploy role. If that’s the PAT role, you can skip the schema-object grants; they also require the schema to exist first, so run them only after a deploy creates it (or after you create the schema yourself as the deploy role, CREATE SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>).
  • Owner is a different role (bring-your-own schema, or the PAT role differs from the deploy role) → grant the PAT role the missing privileges:
GRANT USAGE ON WAREHOUSE <PAT_WAREHOUSE> TO ROLE <PAT_ROLE>;
GRANT USAGE ON DATABASE <DEPLOYMENT_DB> TO ROLE <PAT_ROLE>;
GRANT USAGE ON SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT CREATE TABLE, CREATE STAGE
ON SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT SELECT, INSERT, DELETE, TRUNCATE
ON ALL TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT SELECT, INSERT, DELETE, TRUNCATE
ON FUTURE TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT SELECT
ON ALL DYNAMIC TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT SELECT
ON FUTURE DYNAMIC TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT SELECT
ON ALL VIEWS IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT SELECT
ON FUTURE VIEWS IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT READ, WRITE
ON ALL STAGES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;
GRANT READ, WRITE
ON FUTURE STAGES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <PAT_ROLE>;

For ordinary grants, ON ALL covers existing objects and ON FUTURE covers new objects. Both matter when deployment or refresh creates tables under a different owner role. Stage READ and WRITE apply to internal stages used for bulk uploads. Retain the task and Native App access grants from the PAT authentication setup for the deployment role as well.

In PyRel 1.36.0 and later, a PAT role that doesn’t run the deploy also needs meta-schema grants. A prescriptive Problem’s solver inputs and result tables live in <DEPLOYMENT_SCHEMA>_META, which rai models deploy creates under the deploy role. If deployment.meta_schema names another schema, use its fully qualified name in place of <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META here and in step 4. If that schema is in another database, also repeat the database USAGE grants from steps 3 and 4 for it. Grant the following after a deploy:

GRANT USAGE ON SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META TO ROLE <PAT_ROLE>;
GRANT SELECT
ON ALL TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META TO ROLE <PAT_ROLE>;
GRANT SELECT
ON ALL VIEWS IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META TO ROLE <PAT_ROLE>;
-- Once for each solver result table (seven per Problem) listed by:
-- SHOW TABLES LIKE '%SOLVERRESULT%' IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META;
GRANT INSERT, DELETE, TRUNCATE
ON TABLE <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META.<SOLVERRESULT_TABLE> TO ROLE <PAT_ROLE>;

Grant table writes in the meta schema one result table at a time, never ON ALL TABLES or ON FUTURE TABLES. The caution in step 4 explains why. The PAT role needs no stage privilege in the meta schema.

The following grants allow the reasoner to use the PAT role’s privileges within the deployment schema. They do not give the application unrestricted access as the user.

The deployment schema must exist before you run these grants. Deploy once first, or pre-create the schema as the role that runs rai models deploy (usually the model role, not the shared RAI_DEPLOYED_MODEL) — see the ordering note in step 5.

Caller privilegeWhy it is needed
USAGE on database and schemaResolve and access the deployment’s input and output objects.
USAGE on warehouseRun Snowflake queries and load results using pat_warehouse.
SELECT on tables, dynamic tables, and viewsRead prescriptive inputs and query row counts during data transfer. These are distinct object types and need separate grants.
CREATE TABLE on schemaCreate result tables when required.
INSERT on tablesWrite solver results into output tables.
DELETE, TRUNCATE on tablesClear or replace results during refresh so old rows do not accumulate.
CREATE STAGE on schemaCreate temporary staging areas for bulk uploads of results.
WRITE on internal stagesUpload result files for bulk loading and remove staged files when needed.
READ on internal stagesRead staged files when loading them into result tables.
-- Namespace and query execution.
GRANT CALLER USAGE
ON DATABASE <DEPLOYMENT_DB> TO APPLICATION <RAI_APP_NAME>;
GRANT CALLER USAGE
ON SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO APPLICATION <RAI_APP_NAME>;
GRANT CALLER USAGE
ON WAREHOUSE <PAT_WAREHOUSE> TO APPLICATION <RAI_APP_NAME>;
-- Input reads and row-count queries.
GRANT INHERITED CALLER SELECT
ON ALL TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>
TO APPLICATION <RAI_APP_NAME>;
GRANT INHERITED CALLER SELECT
ON ALL DYNAMIC TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>
TO APPLICATION <RAI_APP_NAME>;
GRANT INHERITED CALLER SELECT
ON ALL VIEWS IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>
TO APPLICATION <RAI_APP_NAME>;
-- Write and replace results.
GRANT INHERITED CALLER INSERT, DELETE, TRUNCATE
ON ALL TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>
TO APPLICATION <RAI_APP_NAME>;
GRANT CALLER CREATE TABLE, CREATE STAGE
ON SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>
TO APPLICATION <RAI_APP_NAME>;
-- Bulk uploads and loading staged files into result tables.
GRANT INHERITED CALLER READ, WRITE
ON ALL STAGES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>
TO APPLICATION <RAI_APP_NAME>;

Unlike ordinary GRANT ... ON ALL, GRANT INHERITED CALLER ... ON ALL covers both current and future objects in the schema. No separate future caller grant is needed. If the deployment schema is dropped and recreated, reapply schema-scoped grants.

In PyRel 1.36.0 and later, add caller grants on the meta schema. The solver reads its inputs from <DEPLOYMENT_SCHEMA>_META, or the schema deployment.meta_schema names, and writes its results there. Without these grants the solve fails with connector.other. Run them after a deploy:

GRANT CALLER USAGE
ON SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META TO APPLICATION <RAI_APP_NAME>;
GRANT INHERITED CALLER SELECT
ON ALL TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META
TO APPLICATION <RAI_APP_NAME>;
GRANT INHERITED CALLER SELECT
ON ALL VIEWS IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META
TO APPLICATION <RAI_APP_NAME>;
-- Large results are loaded through a temporary stage in the meta schema.
GRANT INHERITED CALLER READ, WRITE
ON ALL STAGES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META
TO APPLICATION <RAI_APP_NAME>;
-- Once for each solver result table (seven per Problem) listed by:
-- SHOW TABLES LIKE '%SOLVERRESULT%' IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META;
GRANT CALLER INSERT, DELETE, TRUNCATE
ON TABLE <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META.<SOLVERRESULT_TABLE>
TO APPLICATION <RAI_APP_NAME>;

Result-table names depend on the model and Problem names and on the deployment’s object-name prefix and suffix. A redeploy that changes none of these keeps the tables and their grants. Otherwise, and for a new Problem, rerun the meta-schema grants from steps 3 and 4 after the deploy.

Use TO APPLICATION <RAI_APP_NAME> for the Native App’s caller grants — that is all a released (marketplace) app install needs.

After completing setup, deploy the model using its normal deployment role and active profile:

Terminal window
rai models deploy --wait

The model should declare prescriptive execution with problem.configure(...), not problem.solve(...). --wait waits for the first refresh triggered by the deployment. If run_on_deploy is disabled, use your normal refresh mechanism; deployment alone does not test the PAT connection.

Confirm the refresh succeeded and the outputs are populated. The meta schema’s REFRESH_STATUS procedure reports each schedule’s state, and you can query the output tables directly:

CALL <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META.REFRESH_STATUS(); -- expect SUCCEEDED
SELECT * FROM <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>.<OUTPUT_TABLE>; -- your model's output

See Monitor a deployment from SQL for more. Also check any deployment warnings: Refresh Tasks Cannot Run means task privileges are missing, and PyRel may skip waiting instead of running a refresh. A successful local Snowflake login does not verify the reasoner’s compute-pool network access or caller grants.

Start with the failed refresh’s error, then look up the prescriptive job if a job ID was created:

Terminal window
rai jobs list --type Prescriptive --id <JOB_ID>

Jobs started by refresh tasks belong to the SYSTEM user. Use rai jobs list for these jobs; rai jobs get is not available to the deploying user for them.

Some failures currently surface as generic job failures without identifying the underlying cause. More specific error reporting is being improved. A generic failure alone does not distinguish a PAT, network-policy, or permissions issue; use the available diagnostics below to investigate.

For broader deployment diagnostics, run rai doctor. To collect a report, use rai doctor report --output report.zip; inspect refresh_log.csv for failed runs and their error messages. In Snowsight, inspect task history when no refresh started, login history for PAT connection failures, and query history for permission failures after login. Use the refresh time and PAT user to narrow the search; an administrator may be needed to see these records.

For prescriptive workloads, follow the grant and refresh ordering above before deploying. After completing setup and configuring your model’s target schema and refresh schedule as described in Deploy a model, deploy using your normal deployment role and active profile:

Terminal window
rai models deploy --wait

--wait waits for the first refresh triggered by deployment. If run_on_deploy is disabled, trigger a refresh using your configured schedule instead.

Replace the placeholders below with your deployment database, schema, and an output table. Check the schedule state and query an output table:

CALL <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META.REFRESH_STATUS();
SELECT * FROM <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>.<OUTPUT_TABLE>;

A SUCCEEDED refresh and the expected output confirm the refresh completed for that schedule. A successful CLI login or deployment alone does not prove the reasoner exercised PAT access. See Monitor a deployment from SQL to inspect refresh state and errors. For failed prescriptive refreshes, use the troubleshooting steps above.