Skip to content

fix(grants): order READ before WRITE on bulk stage grants#23

Merged
noel merged 1 commit into
datacoves:mainfrom
kellerbrown:fix/bulk-stage-read-write-ordering
Jun 18, 2026
Merged

fix(grants): order READ before WRITE on bulk stage grants#23
noel merged 1 commit into
datacoves:mainfrom
kellerbrown:fix/bulk-stage-read-write-ordering

Conversation

@kellerbrown

@kellerbrown kellerbrown commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Snowflake refuses to grant WRITE on a stage to a role that does not already
hold READ on it. The privileges have to be granted in order, or together. This
rule also covers ALL/FUTURE grants:

GRANT WRITE ON FUTURE STAGES IN DATABASE db TO ROLE r;
-- SQL compilation error: Privilege order violation for the future grants on
-- stage. READ should be granted before/simultaneously with WRITE.

Snowcap already accounts for this with Blueprint._create_stage_privilege_refs,
which makes every WRITE grant depend on the matching READ grant so the two
land at different dependency levels and apply in the right order. But it only
catches grants on a single named stage. Bulk grants over ALL/FUTURE stages
are missed, and the apply fails.

Root cause

_create_stage_privilege_refs collects grants by checking on_type == STAGE:

if resource._data.on_type == ResourceType.STAGE:
    stage_grants.setdefault(resource._data.on, []).append(resource)

That only holds for a grant on a specific stage (GRANT WRITE ON STAGE db.sc.s).
For a bulk grant the parser puts the container in on_type and the stage in
items_type:

grant on_type items_type
WRITE ON STAGE db.sc.s STAGE None
WRITE ON ALL STAGES IN DATABASE db DATABASE STAGE
WRITE ON FUTURE STAGES IN SCHEMA db.sc SCHEMA STAGE

So bulk stage grants never enter stage_grants, no WRITE -> READ dependency is
created, and the two grants end up at the same dependency level. At apply time
same-level grants for a role run concurrently (execute_commands_in_parallel),
so WRITE can reach Snowflake before READ and the statement fails.

Fix

Also collect grants whose items_type is STAGE, and group reads and writes by
the full scope so only grants over the same set of stages get linked:

d = resource._data
if d.on_type == ResourceType.STAGE or d.items_type == ResourceType.STAGE:
    key = (d.on_type, d.on, d.grant_type, d.items_type)
    stage_grants.setdefault(key, []).append(resource)

The existing WRITE -> READ pass is unchanged; it now runs over bulk grants too.
This covers all ALL/FUTURE × DATABASE/SCHEMA combinations.

Testing

All CI gates pass: ruff check snowcap/, mypy typecheck, and the full unit
suite (pytest --ignore=tests/integration, 1533 passed). The existing
single-named-stage integration test
(test_stage_read_write_privilege_execution_order) still passes against a live
account, confirming no regression on that path.

Unit test

Added test_blueprint_bulk_stage_read_write_ordering in tests/test_blueprint.py.
The fix is container- and grant-type-agnostic (it keys on items_type == STAGE),
so the test asserts the WRITE -> READ dependency for all four
ALL/FUTURE × DATABASE/SCHEMA combinations after Blueprint._finalize, and
asserts distinct scopes do not cross-link (a WRITE only depends on the
READ over its exact same scope). Verified it fails on main (bulk WRITE
carries no READ dependency) and passes with the fix.

Integration test

Added test_bulk_stage_read_write_privilege_execution_order in
tests/integration/test_blueprint.py (marked requires_snowflake). It applies
READ/WRITE across all four ALL/FUTURE × DATABASE/SCHEMA scopes to a
role and asserts the apply succeeds. Confirmed end-to-end: on main the apply
raises the real Snowflake error — 003511 (23001): Privilege order violation for the future grants on stage. READ should be granted before/simultaneously with WRITE — and with the fix the same apply succeeds.

_create_stage_privilege_refs only linked grants on a single named stage
(on_type == STAGE), so bulk ALL/FUTURE stage grants got no WRITE -> READ
dependency. At apply time the same-level grants for a role run
concurrently and WRITE could reach Snowflake before READ, failing with
"Privilege order violation for the future grants on stage."

Also collect grants whose items_type is STAGE and key reads/writes by the
full scope (on_type, on, grant_type, items_type) so only grants over the
same set of stages get linked. Covers all ALL/FUTURE x DATABASE/SCHEMA
combinations.

Add unit coverage for all four bulk scopes plus cross-scope non-linking,
and an integration test that applies READ/WRITE across all four scopes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kellerbrown kellerbrown changed the title fix: order READ before WRITE on bulk stage grants fix(grants): order READ before WRITE on bulk stage grants Jun 18, 2026
@noel
noel merged commit a85765e into datacoves:main Jun 18, 2026
4 checks passed
@noel

noel commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Thanks for the thorough fix and excellent PR description, Keller! The root cause analysis and test coverage are great.

@noel

noel commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

@kellerbrown deployed a new version with this change.
I would like more feedback on snowcap, let's connect on LinkedIn
https://www.linkedin.com/in/noelgomez/

@kellerbrown

kellerbrown commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

@kellerbrown deployed a new version with this change. I would like more feedback on snowcap, let's connect on LinkedIn https://www.linkedin.com/in/noelgomez/

Thank you! Wanted to also give you a heads up that although the README.md links to the issues for the repo, it seems contributors outside of Datacoves are unable to create them.

Loving what your team is doing with Snowcap thus far. Sent you a request to connect on LinkedIn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants