Modern data platforms are getting serious about governed data sharing. Snowflake's Internal Marketplace lets you publish data products once and let teams across the organization discover, request, and consume them — with access controls, tagging, and a defined approval step baked in.
The problem? "A defined approval step" is singular. One step.
In real enterprises, approving access to a sensitive data product rarely involves just one person. You need the requester's line manager to sign off. Then the data owner. Then the security or DPO team. Sometimes finance. Depending on the data sensitivity and regulatory context, the chain might be two steps or six.
The standard answer has been: bolt on the corporate ticketing system. Ticket created → manager approves → data team manually grants access. It works, but it has serious cracks — and this post shows a cleaner alternative built entirely inside Snowflake, with an AI coding assistant as a genuine collaborator.
The Ticketing Workaround and Why It Falls Short
The ticketing integration is popular because majority of the enterprises already has its own ticketing systems. But look at what actually happens:
- Context is split. The approval conversation lives in the ticket. The data product, its schema, and what it contains live in Snowflake or other platforms. Approvers approve in the dark.
-
The grant is still manual. Someone on the data team reads a resolved ticket and runs a
GRANTcommand manually. That is tedious, and it is error-prone. - There is no enforcement link. A ticket marked "approved" does not cause access to be granted. The two systems are not connected. Access can be granted without a ticket. Tickets can close without access being granted.
- Audit is fragmented. Compliance teams need to answer "who approved what, when, and why?" That answer now lives across two systems, in two schemas, with two different retention policies.
- Data governance lives outside the governance platform. You have invested in a data cloud with RBAC, row-level security, and dynamic data masking — and the access approval step, the moment that matters most for governance, happens in a system that knows nothing about your data.
The ticketing workaround is a symptom of the native request flow's limitation, not a solution to the underlying governance need.
A Better Idea: Keep Approval Inside the Data Cloud
What if the approval workflow ran inside Snowflake? Same identity plane, same RBAC, same audit trail, zero data egress to an external system.
This is now possible with Snowflake App Runtime (public preview): a framework for deploying full-stack web applications — specifically Next.js — directly onto Snowflake's infrastructure via Snowpark Container Services. The app runs inside the Snowflake security perimeter, inherits the enterprise's existing SSO and RBAC, and queries execute as the logged-in user. You get a public HTTPS URL with no separate infrastructure to provision, secure, or maintain.
For a governed data product approval workflow, this is exactly the right host. The app reads and writes Snowflake tables, calls a stored procedure to grant access, and the entire approval chain — state, decisions, audit — lives in a schema alongside the data it governs.
Architecture
Here is the flow we want to build:
And this is the high level architecture for this application:
Data model (five tables)
| Table | Purpose |
|---|---|
DATA_PRODUCT |
Registry of published data products; holds access_role (the role granted on final approval) |
APPROVAL_CHAIN |
Fixed N-step chain per product: (product_id, step_order, approver_role, step_label)
|
ACCESS_REQUEST |
One row per request: status, current step, requester |
REQUEST_STEP |
Per-step decision log: who decided, what decision, any comment |
AUDIT_LOG |
Append-only event trail for compliance |
The chain is keyed by product and step order, so each product can have a different chain (a public dataset might need one step; a PII-classified product might need four).
The Governance Model — the Part That Matters for Enterprise
Three pillars make this design enterprise-grade.
1. Least Privilege: The App Never Holds MANAGE GRANTS
The final approval triggers an actual GRANT command. Giving the application service role MANAGE GRANTS directly would be a serious over-privilege — any bug or compromised endpoint could grant arbitrary access to anything.
Instead, use an owner's-rights stored procedure owned by a dedicated privileged role:
CREATE OR REPLACE PROCEDURE GOVERNANCE.APPROVAL.GRANT_PRODUCT_ACCESS(P_REQUEST_ID STRING)
RETURNS STRING
LANGUAGE SQL
EXECUTE AS OWNER -- runs with owner's MANAGE GRANTS, not caller's
AS
BEGIN
-- Verify the request is actually approved (guard against direct calls)
LET v_status STRING;
LET v_access_role STRING;
LET v_requester STRING;
SELECT status, p.access_role, r.requester
INTO :v_status, :v_access_role, :v_requester
FROM GOVERNANCE.APPROVAL.ACCESS_REQUEST r
JOIN GOVERNANCE.APPROVAL.DATA_PRODUCT p USING (product_id)
WHERE request_id = :P_REQUEST_ID;
IF (v_status != 'APPROVED') THEN
RETURN 'ERROR: request not in APPROVED status';
END IF;
EXECUTE IMMEDIATE 'GRANT ROLE ' || :v_access_role || ' TO USER ' || :v_requester;
INSERT INTO GOVERNANCE.APPROVAL.AUDIT_LOG (event_type, request_id, detail)
VALUES ('ACCESS_GRANTED', :P_REQUEST_ID, :v_access_role || ' -> ' || :v_requester);
RETURN 'granted';
END;
The app service role gets only GRANT USAGE ON PROCEDURE ... TO ROLE APP_SERVICE_ROLE. It can call the procedure. It cannot grant anything else.
2. Separation of Duties: Steps Are Defined by Role, Not by Specific User
Naming specific approvers creates fragility (people leave, change roles to other departments) and is operationally painful. Instead, each step in the APPROVAL_CHAIN specifies an approver_role — any member of that role can act on that step.
The approver queue is filtered server-side using IS_ROLE_IN_SESSION:
SELECT
r.request_id,
r.requester,
p.name AS product_name,
r.current_step,
r.business_reason
FROM GOVERNANCE.APPROVAL.ACCESS_REQUEST r
JOIN GOVERNANCE.APPROVAL.DATA_PRODUCT p USING (product_id)
JOIN GOVERNANCE.APPROVAL.APPROVAL_CHAIN c
ON c.product_id = r.product_id
AND c.step_order = r.current_step
WHERE r.status LIKE 'PENDING%'
AND IS_ROLE_IN_SESSION(c.approver_role) -- only show steps this user can act on
ORDER BY r.created_at;
The same guard runs in the decision handler: if IS_ROLE_IN_SESSION(approver_role) is false for the current step, the request returns a 403 forbidden error. A user who holds the Step 1 role cannot approve Step 2, and so on.
This works naturally with App Runtime's identity model: because the app queries run as the logged-in user, IS_ROLE_IN_SESSION reflects that user's actual granted roles. No impersonation, no separate identity mapping.
3. Auditability: Append-Only Trail for Every Decision
Every state change — submission, per-step decision, final grant — writes to AUDIT_LOG with actor (username), actor_role (CURRENT_ROLE()), event_type, step_order, decision, comment, and created_at. The table has no UPDATE or DELETE grants on the app service role. It is write-once.
This is the record compliance teams need: "who approved access to this PII dataset, what role did they hold, and when?"
Building It with Cortex Code — an AI Collaborator That Knows the Platform
This workflow was designed and built with Cortex Code (CoCo), Snowflake's AI coding assistant, as a genuine co-author — not just a code completer.
Plan mode: describe the problem, get a reviewable design
The starting prompt was roughly:
"I need a multi-step data product approval workflow beside the Internal Marketplace. Native request flow is single-step. I want: requester submits, N approvers in sequence (keyed by role), final approval triggers a real Snowflake GRANT, full audit log. What is the most secure architecture? What are my options for the grant step?"
CoCo did not write code immediately. It entered plan mode:
- It read the App Runtime and RBAC documentation
- Identified the least-privilege grant procedure pattern
- Flagged that personal-database deploys cannot be shared with other roles (a non-obvious constraint that would have broken the application)
- Produced a full implementation plan — data model, RBAC design, UI routes, security tradeoffs — before a single file was created
The plan could be reviewed and adjusted before any work started.
This is the part that changes how platform engineers work. The design decision ("never give the app MANAGE GRANTS; use an owner's-rights procedure") came from the assistant's (CoCo) research, not from prior knowledge we had to bring in.
Scaffold: from plan to working code
With the plan approved, CoCo:
- Generated the three SQL setup scripts (data model, RBAC + grant procedure, and seed data) against the confirmed schema design.
- Scaffolded the Next.js App Runtime project, wired the data platform connection to the OAuth token injected by the runtime environment, and built the requester and approver UI routes.
- Wrote the backend API routes for catalog browsing, request submission, approver queue (with the
IS_ROLE_IN_SESSIONfilter), and the decision handler with step-advance logic.
I am not a Next.js developer and I don't have any knowledge or experience in developing full-stack web application. The entire frontend was written by CoCo based on iterative prompts describing the UX — product cards, a horizontal step progress indicator, approve/reject buttons with a comment field.
Iterate: role-switcher for demo purpose, polish for stakeholders
After the initial deploy, there were two follow-up tasks:
Role isolation for the demo. Because I have ACCOUNTADMIN role in my account,
IS_ROLE_IN_SESSIONreturned true for every step — making the multi-step story invisible. Prompt: "All approval steps show to every user in demo because ACCOUNTADMIN holds all roles. I need an in-app role switcher so I can show the approval chain step by step in a presentation." CoCo designed aqueryAs(role, sql)helper that runsUSE SECONDARY ROLES NONE; USE ROLE <role>before each approval-side query, scoped the switcher behind an allowlist (preventing SQL injection via role names, which cannot be parameter-bound), and added a<select>dropdown to the top bar.UI polish. A full professional redesign — CSS token system, sidebar navigation, connected horizontal stepper component, loading skeletons, identity chip — from a short prompt describing the visual direction. No CSS knowledge required.
Each iteration followed the same cycle: describe the need in plain language → CoCo proposes a plan → review → approve → execute. The final app took a working session, not a sprint.
What You Get
| Capability | Detail |
|---|---|
| N-step approval chains | Configurable per data product; different products can have different chains |
| In-platform governance | State, decisions, and grants in Snowflake — same audit, same RBAC |
| Least-privilege grant | App never holds MANAGE GRANTS; owner's-rights proc is the only grant path |
| Separation of duties | Role-keyed steps; IS_ROLE_IN_SESSION enforced server-side on every request |
| Full audit trail | Append-only log, every decision, actor + role captured |
| Easy revoke | One REVOKE ROLE <access_role> FROM USER <requester> undoes all grants for that product |
| No ticketing integration | No context split, no manual grant step, no fragmented audit |
Limitation and Future Development
App Runtime is in public preview. Features and deployment mechanics may change before GA. It is not available on trial accounts — you need a paid Snowflake account. Check the release notes before committing to this pattern for production.
This sits beside the native marketplace, not inside it. The native Internal Marketplace discovery flow (browsing listings, the "Request Access" button) still exists. This custom app replaces the request and approval interaction, not the catalog itself. If you want users to discover via the native catalog, the custom app is the entry point they click to actually submit — or you maintain a parallel catalog view in the app.
Fixed-N chains. The design above uses a fixed approval chain per product, defined at product registration time. Dynamic routing (e.g., "if the dataset is tagged PII add the DPO step; otherwise skip it") is an extension — the APPROVAL_CHAIN table structure supports it, but the routing logic would need to be added.
Wrap-Up
Internal Marketplaces are the right direction for governed data sharing at scale. But the single-step approval flow is a real constraint for enterprises with multi-department governance requirements. The pattern shown here — Snowflake tables for state, an owner's-rights procedure for least-privilege grants, App Runtime for the UI layer, and role-based step keying for separation of duties — keeps the entire approval chain inside the data platform where it belongs.
If you build this or a variant of it, the governance story becomes: every access decision was made in the platform, by an authenticated user holding the right role, with a complete audit trail, and the grant was executed by a stored procedure with no human on the keyboard.
That is a story your compliance team can work with.
Want to try it? Snowflake App Runtime + Cortex Code is available on public preview today. Describe your approval chain in plain language and let CoCo build the plan — you review before a single line of code is written.


Top comments (0)