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:
- 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.
- 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.
- 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.
Governing model inputs and outputs
Section titled “Governing model inputs and outputs”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.
Try it out
Section titled “Try it out”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:
- 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. - 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).
- 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 fallbackGRANT 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 modeldeployment: role: MODEL_A_MANAGER_ROLE # same model role schema: RAI_MODEL_SHARED.MODEL_A_SCHEMA # the <model_db>.<model_schema> the script createdSee Deploy a model for the full connection setup and the deploy command.
Governed setup (recommended for production)
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.
One-time account setup
Section titled “One-time account setup”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.ACCOUNTADMINkeeps 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 VARCHARLANGUAGE SQLEXECUTE AS CALLER -- REQUIRED: body switches role; caller must be ACCOUNTADMINASDECLARE 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 VARCHARLANGUAGE SQLEXECUTE AS CALLER -- caller must be ACCOUNTADMIN (needs MANAGE GRANTS on the deploy role)ASDECLARE 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);Per-role setup
Section titled “Per-role setup”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);Configure the deployment connection
Section titled “Configure the deployment connection”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.comauthenticator selects how the CLI signs in to Snowflake. See
Configure Snowflake authentication for the full
setup of each option:
- External browser — SSO via the browser (the default above).
- Programmatic access token — a PAT read from a token file; use this if your account enforces MFA.
- Key-pair — JWT signed with a private key.
- Username / password, optionally with MFA.
- OAuth.
PAT authentication
Section titled “PAT authentication”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.
- Bind the
iceberg_eaireference to that EAI so the app can attach it to the engine. This is a one-time call per app install, not per model:This grants the appCALL <APP>.app.update_reference('iceberg_eai','SET',SYSTEM$REFERENCE('EXTERNAL ACCESS INTEGRATION','<RAI_EAI>','PERSISTENT','USAGE'));USAGEon the EAI and lets the deploy attachreference('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:::
Prescriptive needs extra setup on top of this section — two additional config fields
(pat_user, pat_warehouse), a compute-pool network rule, and Native App caller grants. After
completing PAT authentication here, continue with
Additional setup for the prescriptive reasoner.
Admin setup
Section titled “Admin setup”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.
The script uses Snowflake session variables.
The deploy role is loaded automatically from the config table. Session variables are reset when your
session ends, so re-run the SET statements if you reconnect.
-- ================================================================-- 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 VARCHARLANGUAGE SQLEXECUTE AS CALLER -- caller must be ACCOUNTADMIN (needs CREATE INTEGRATION and MANAGE GRANTS)ASDECLARE 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 VARIANTLANGUAGE SQLEXECUTE AS CALLERASDECLARE 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 rotationBEGIN 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);Deployment user setup
Section titled “Deployment user setup”- 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.
Today, Snowflake lets the logic engine’s PAT logins through a block-all policy without a compute-pool allow rule. Snowflake plans to change this. When it does, logic and Iceberg models will need a compute-pool allow rule like the one in Step 2, or their refreshes will fail to authenticate.
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;A user-level network policy applies to all of that user’s authentication, not just PATs. This
block-all policy will lock a human user out of Snowsight and external tools — only attach it to a
user dedicated to deployments. It also overwrites any policy already on the user; check first with
SHOW PARAMETERS LIKE 'NETWORK_POLICY' FOR USER <user>;
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.
Developer setup
Section titled “Developer setup”- 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:
- Self-issued (simplest, admin-free rotations). Issue the token for the developer’s own user.
Managing your own token needs no special privilege, so the developer runs
RAI_PAT_SETUPdirectly and every rotation needs no admin. The first-time setup still needs an admin once, though: Snowflake only lets a user authenticate with a PAT when a network policy applies to them, attaching a policy requires ownership of the user, and the page’s block-all policy would lock the developer out — so an admin must first attach a policy that allows the developer’s own access. Trade-offs: the token authenticates as that person; anyone with the model role can read it (the deploy exposes it through<schema>_META.RAI_PAT_SECRET_FN()); and the secret is per model role, so it holds whoever last ran setup successfully — for prescriptive models,pat_usermust name that same person. Good for evaluation or small teams. - Dedicated service account (recommended for production). Issue the token for a dedicated,
non-interactive user (see Deployment user setup). Its block-all
network policy stops it from logging in, so it can’t run anything itself —
ACCOUNTADMINrunsRAI_PAT_SETUPfor it, first time and every rotation. Nothing authenticates as a person, and the token isn’t tied to anyone’s account. No token-management delegation is needed: the admin already has authority over the account’s tokens.
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:
At deploy time PyRel creates a UDF that reads the token from the secret
(get_generic_secret_string), which requires READ on the secret. The procedure grants the model
role both USAGE and READ, so the deploy can read the token even when the secret is owned by a
different role (e.g. ACCOUNTADMIN ran the call). Without READ the deploy fails with
Secret '<name>' does not exist or operation not authorized.
-- 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 key | Source | Value (default names) |
|---|---|---|
deployment.role (also the connection role) | returned deploy_role | your model role |
deployment.pat_secret | returned pat_secret | RAI_MODEL_SHARED.RAI_SYSTEM_SCHEMA.RAI_PAT_<MODEL_ROLE>_SECRET |
deployment.pat_eai | returned pat_eai | RAI_EAI |
deployment.pat_user | you supply (prescriptive only) | the token owner (the deployment user) |
deployment.pat_warehouse | you 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.
Other reasoners run under the deploying user’s identity through the transaction path. The prescriptive reasoner additionally connects back into Snowflake from its compute pools using the caller’s credentials to read inputs and bulk-load results. That requires compute-pool network access and a set of Native App caller grants on top of the base PAT setup.
End-to-end setup order
Section titled “End-to-end setup order”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.
- Model deployment infrastructure (admin, once per account) — roles, shared DB, task/warehouse grants. See Governed setup.
- PAT infrastructure (admin, once per account) — EAI, network policy, deployment-user policy. See Admin setup.
- PAT + secret (per model) — role-restricted token stored as a secret. See Developer setup.
- Configure all four PAT fields — step 1 below.
- Allow the reasoner’s compute pools through the network policy — step 2 below.
- PAT-role privileges and Native App caller grants — steps 3 and 4 below.
- Deploy and verify a refresh — step 5 below.
- Troubleshoot any refresh failure — Troubleshooting.
Before you start
Section titled “Before you start”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):
| Placeholder | Meaning | Where 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 |
RAI_PAT_BLOCK_POLICY is the default only if you attached it to a dedicated deployment user. If
the PAT user already has a different user- or account-level policy, that policy is the one to
modify. Confirm before changing anything:
SHOW PARAMETERS LIKE 'NETWORK_POLICY' FOR USER <PAT_USER>; -- user-level takes precedenceSHOW PARAMETERS LIKE 'NETWORK_POLICY' IN ACCOUNT; -- fallback if the user has noneHave 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.
Use a dedicated deployment schema: the schema-wide grants below allow reads and result replacement across its tables, including deletion of existing rows. Do not apply them to a schema containing unrelated data. They are a schema-scoped setup, not a claim that every model needs access to every object in the schema.
1. Configure all four PAT fields
Section titled “1. Configure all four PAT fields”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_secretmust contain a valid PAT belonging topat_user.pat_eaimust 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_usermust identify the token’s owner, not simply the user running the CLI.pat_warehousemust 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_LISTpool names are single-quoted strings.ALLOWED_NETWORK_RULE_LISTtakes a parenthesized list of network-rule identifiers. The parentheses are what matter: a bare quoted name without them (= 'db.schema.rule') fails withinvalid 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 identifierIf 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.
Snowflake currently documents ALTER NETWORK POLICY ... ADD as a preview
available on AWS. Where ADD is unavailable, use SET ALLOWED_NETWORK_RULE_LIST
with the complete existing allowed-rule list plus the new rule. SET
replaces the list; setting only the new rule can remove existing access. Inspect
the resulting policy and any blocked compute-pool rules for conflicts.
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:
- The PAT user’s restricted role must be allowed to perform the operation.
- 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>;Owning the deployment schema does not give the PAT role USAGE on the pat_warehouse
(nor access to objects another role created in the schema — see below). The solver connects as the PAT
role and runs on pat_warehouse, so this grant is needed even when the PAT role owns
the schema (unless it already has warehouse USAGE from elsewhere):
GRANT USAGE ON WAREHOUSE <PAT_WAREHOUSE> TO ROLE <PAT_ROLE>;Skipping it fails the solve with a generic connector.other error.
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 runsrai 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 deploywill 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.
4. Grant the Native App caller access
Section titled “4. Grant the Native App caller access”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 privilege | Why it is needed |
|---|---|
USAGE on database and schema | Resolve and access the deployment’s input and output objects. |
USAGE on warehouse | Run Snowflake queries and load results using pat_warehouse. |
SELECT on tables, dynamic tables, and views | Read prescriptive inputs and query row counts during data transfer. These are distinct object types and need separate grants. |
CREATE TABLE on schema | Create result tables when required. |
INSERT on tables | Write solver results into output tables. |
DELETE, TRUNCATE on tables | Clear or replace results during refresh so old rows do not accumulate. |
CREATE STAGE on schema | Create temporary staging areas for bulk uploads of results. |
WRITE on internal stages | Upload result files for bulk loading and remove staged files when needed. |
READ on internal stages | Read 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>;Grant INSERT, DELETE, and TRUNCATE in the meta schema only on the solver result tables, never
ON ALL TABLES. The meta schema also holds _INTERNAL_PLAN, whose SQL the refresh task runs with
the deploying role’s full privileges. Write access to it would let whoever holds it change what
every refresh runs.
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.
If the RAI Native App is installed in development mode (from source, unpublished —
common for internal/test accounts), its executables run in the security context of the
app’s owner role, and Snowflake requires that role to hold the same caller grants
as the application. Otherwise the solve fails with connector.other and a service-log
error like: “the owner role ‘<OWNER>’ of the development mode application ‘<app>’
must have at least the same set of caller grants as the application.”
Find the owner role, then repeat every grant above with TO ROLE <owner> instead of
TO APPLICATION <RAI_APP_NAME>:
-- 1) find the app's owner role (often RAI_CONSUMER on dev installs)SHOW GRANTS ON APPLICATION <RAI_APP_NAME>; -- the OWNERSHIP row names the role
-- 2) mirror each caller grant to that role, e.g.:GRANT CALLER USAGE ON DATABASE <DEPLOYMENT_DB> TO ROLE <OWNER_ROLE>;GRANT CALLER USAGE ON SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <OWNER_ROLE>;GRANT CALLER USAGE ON WAREHOUSE <PAT_WAREHOUSE> TO ROLE <OWNER_ROLE>;GRANT INHERITED CALLER SELECT ON ALL TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <OWNER_ROLE>;GRANT INHERITED CALLER SELECT ON ALL DYNAMIC TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <OWNER_ROLE>;GRANT INHERITED CALLER SELECT ON ALL VIEWS IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <OWNER_ROLE>;GRANT INHERITED CALLER INSERT, DELETE, TRUNCATE ON ALL TABLES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <OWNER_ROLE>;GRANT CALLER CREATE TABLE, CREATE STAGE ON SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <OWNER_ROLE>;GRANT INHERITED CALLER READ, WRITE ON ALL STAGES IN SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA> TO ROLE <OWNER_ROLE>;-- 3) in PyRel 1.36.0 and later, also mirror the meta-schema caller grants (table writes stay per table)A released app has its own self-contained identity and does not need this — skip this box entirely in production.
5. Deploy and verify a refresh
Section titled “5. Deploy and verify a refresh”Steps 2–4 (network rule, PAT-role privileges, caller grants) all reference the deployment
schema, and the first refresh exercises them. But rai models deploy is what creates
the schema. In PyRel 1.36.0 and later, use the first order below: only a deploy creates the meta
schema and result tables that steps 3 and 4 grant on. Before 1.36.0, either order works:
- Deploy first, then grant, then refresh — run
rai models deploy(it creates the schema + objects; with--waitthe first refresh will fail if the grants aren’t in yet), apply steps 2–4 against the now-existing schema, then re-run the refresh (see below). - Pre-create, then grant, then deploy — create the schema as the deploy role
(
CREATE SCHEMA <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>), apply steps 2–4, thenrai models deploy --waitso the first refresh already succeeds.
To upgrade an existing prescriptive deployment to 1.36.0, redeploy, add the meta-schema grants, then re-run the refresh.
Re-run a refresh at any time. Mind the schedule-name casing — it differs between the two calls:
- The refresh task is named
REFRESH_<SCHEDULE>with the schedule name uppercased, so both a schedule namedstandardand one namedStandardmap toREFRESH_STANDARD:EXECUTE TASK <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META.REFRESH_STANDARD; -- uppercased schedule name - The refresh procedure takes the schedule name exactly as spelled in
raiconfig.yaml— it is case-sensitive. A mismatched case (for example'STANDARD'when the config saysstandard) returnsRefresh completewithout running anything. A plain re-deploywith an unchanged model may also replay the last result instead of running fresh, so to force a new run:CALL <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>_META._INTERNAL_REFRESH_MODEL('standard', NULL); -- exact raiconfig spelling
After completing setup, deploy the model using its normal deployment role and active profile:
rai models deploy --waitThe 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 SUCCEEDEDSELECT * FROM <DEPLOYMENT_DB>.<DEPLOYMENT_SCHEMA>.<OUTPUT_TABLE>; -- your model's outputSee 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.
Troubleshooting
Section titled “Troubleshooting”Start with the failed refresh’s error, then look up the prescriptive job if a job ID was created:
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.
Deploy and check a refresh
Section titled “Deploy and check a refresh”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:
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.