Skip to content

ADR-0014 — Modular resource scope : schema / database / account

Date : 2026-05-31 Status : Accepted


Context

The provider deploys two distinct categories of objects:

  1. CI/CD deployments (via CLI on Git runners) — deploys the full object graph: schemas, databases, account-level resources (warehouses, roles, integrations...).

  2. Runtime provisioning (via Python API inside PINKY.BILLING.PROVISION_CLIENT() SP) — deploys a client schema dynamically at webhook time (new subscriber). Runs inside Snowflake via EXECUTE AS OWNER, called from ARTIFACT_REPOSITORY.

These two contexts have different requirements: - The SP needs only schema-scoped objects — importing the full provider is unnecessary and unsafe. - The CLI needs all resource types. - The Native App (PINKY on Marketplace) must import only what it needs for provisioning.


Decision

Split provider resources into three scope modules. The cli/ module is never imported inside a SP.

pinky_provider/
├── core/           ← diff engine, manifest parser, session (shared)
├── resources/
│   ├── schema/     ← objects that live in a schema (see list below)
│   ├── database/   ← objects that live in a database
│   └── account/    ← objects that live at account level
├── dag/            ← meta-resource DAG (generates tasks — schema scope)
└── cli/            ← click + rich (never imported in a SP or Native App)

Schema scope — pinky_provider.resources.schema

Objects that can exist inside a client schema. Used by PROVISION_CLIENT().

Resource Note
table
iceberg_table
storage_lifecycle_policy
tag
view
dynamic_table
procedure
user_defined_function
task / task.dag
stage
stream
event_table
alert
streamlit
secret client-delegated credentials — e.g. Pronote API key (Pluto School), INPI token (Pluto Forms). Lives in client schema → deleted at DROP SCHEMA (RGPD by design).

secret is schema-scoped here because each client owns their own credentials. These are not shared infrastructure secrets (those live in PINKY.SECURITY) — they are per-client delegated access tokens.

Database scope — pinky_provider.resources.database

Resource Note
database_role
grant IN DATABASE
schema CREATE SCHEMA at provisioning time

Account scope — pinky_provider.resources.account

Resource Note
database
warehouse
role
user
network_policy
network_rule
secret (shared) PINKY.SECURITY secrets — not client-specific
api_integration
external_access_integration SQL fallback
external_volume
compute_pool
notification_integration
tag

Usage

PROVISION_CLIENT() SP — schema scope only

# ARTIFACT_REPOSITORY_PACKAGES = ('pinky-provider[schema]')
from pinky_provider.resources.schema import SchemaProvider

def provision_client(session, team_id, product_db, client_id, ...):
    provider = SchemaProvider(session)
    provider.apply(
        source = f"{product_db}.PUBLIC.GIT_REPO:/manifest_client/",
        target = f"{product_db}.{client_id}",
        vars   = {"client_id": client_id}
    )
    # Can create: table, view, procedure, stage, secret (client creds)...
    # Cannot create: warehouse, role, user, EAI — physically not in scope

CLI — full scope

# pip install pinky-provider  (full)
from pinky_provider import Provider

provider = Provider(session)
provider.apply(source="manifest.yml", target="MY_DB.PUBLIC")
# All resource types available

Consequences

Positive: - PROVISION_CLIENT() SP imports a lighter wheel → faster cold start via ARTIFACT_REPOSITORY - Physical security boundary: even if the SP is compromised, it cannot create account-level objects - DROP SCHEMA {client_id} cascades client secrets (Pronote, INPI...) → RGPD by design - Clean separation of deployment contexts (CI/CD vs runtime) - Native App (PINKY on Marketplace) imports only schema + database modules — not account

Negative: - Two import paths to maintain (SchemaProvider vs Provider) - manifest_client/ must declare only schema-scoped resources — validation needed at plan time


Rejected alternatives

Single monolithic provider — importing the full provider in PROVISION_CLIENT() works but: - Larger wheel in ARTIFACT_REPOSITORY (unnecessary cold start cost) - No physical enforcement of schema-only scope in the SP - Violates principle of least privilege at the library level