Snowflake logoQuickstart

From Snowsight to First Audit: Governing a Snowflake Schema with CoreModels

You have a Snowflake schema and about thirty minutes. By the end of this tutorial you will have a governed model of that schema in CoreModels — tables as Types, columns as Elements with their native types preserved, declared keys as checks and references, object dependencies as lineage — and you will have run your first drift audit against it and read the result.

From Snowsight to First Audit: Governing a Snowflake Schema with CoreModels

You have a Snowflake schema and about thirty minutes. By the end of this tutorial you will have a governed model of that schema in CoreModels — tables as Types, columns as Elements with their native types preserved, declared keys as checks and references, object dependencies as lineage — and you will have run your first drift audit against it and read the result.

Nothing in this walkthrough requires giving CoreModels credentials to your warehouse. The Snowflake connector (vendor key snowflake, capabilities Import, Audit, and Generate) works entirely from metadata extracts you produce yourself: you run documented queries in Snowsight, save the results as JSON, and upload them. No driver, no network path from our side to your account, and it works the same for every Snowflake edition.

What you need

  • Snowsight access to the schema you want to govern.
  • A CoreModels project and its 32-character hex project id (we'll call it $PROJECT_ID).
  • A bearer token for the CoreModels API ($TOKEN below). Importing requires the Admin role on the project; auditing only needs Viewer.
  • curl and jq, because the artifact payloads are JSON documents carried as strings and jq assembles that cleanly.

The API base URL is written as https://coremodels.example.com throughout — substitute your deployment's host.

Step 1: extract the artifacts

The connector accepts three artifacts. Only the first is required.

information_schema (required). One query joining INFORMATION_SCHEMA.COLUMNS and TABLES. Run it in Snowsight and save the result as information_schema.json:

SELECT ARRAY_AGG(OBJECT_CONSTRUCT(
         'TABLE_CATALOG', c.TABLE_CATALOG, 'TABLE_SCHEMA', c.TABLE_SCHEMA,
         'TABLE_NAME', c.TABLE_NAME, 'TABLE_TYPE', t.TABLE_TYPE,
         'TABLE_COMMENT', t.COMMENT, 'COLUMN_NAME', c.COLUMN_NAME,
         'ORDINAL_POSITION', c.ORDINAL_POSITION, 'DATA_TYPE', c.DATA_TYPE,
         'IS_NULLABLE', c.IS_NULLABLE, 'CHARACTER_MAXIMUM_LENGTH', c.CHARACTER_MAXIMUM_LENGTH,
         'NUMERIC_PRECISION', c.NUMERIC_PRECISION, 'NUMERIC_SCALE', c.NUMERIC_SCALE,
         'COMMENT', c.COMMENT))
FROM INFORMATION_SCHEMA.COLUMNS c
JOIN INFORMATION_SCHEMA.TABLES t
  ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
WHERE c.TABLE_SCHEMA = '<YOUR_SCHEMA>';

keys (optional). Declared primary and foreign keys become uniqueness checks and governed references. Snowflake exposes them through SHOW commands, which you JSON-ify with RESULT_SCAN:

SHOW PRIMARY KEYS IN SCHEMA <YOUR_DB>.<YOUR_SCHEMA>;
SELECT ARRAY_AGG(OBJECT_CONSTRUCT(*)) FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));
-- repeat with: SHOW IMPORTED KEYS IN SCHEMA ... (concatenate both arrays into one)

Save the combined array as keys.json.

object_dependencies (optional). View-to-table and other object dependencies become lineage edges:

SELECT ARRAY_AGG(OBJECT_CONSTRUCT(
         'REFERENCING_DATABASE', REFERENCING_DATABASE, 'REFERENCING_SCHEMA', REFERENCING_SCHEMA,
         'REFERENCING_OBJECT_NAME', REFERENCING_OBJECT_NAME,
         'REFERENCED_DATABASE', REFERENCED_DATABASE, 'REFERENCED_SCHEMA', REFERENCED_SCHEMA,
         'REFERENCED_OBJECT_NAME', REFERENCED_OBJECT_NAME))
FROM SNOWFLAKE.ACCOUNT_USAGE.OBJECT_DEPENDENCIES
WHERE REFERENCING_DATABASE = '<YOUR_DB>';

Save it as object_dependencies.json.

Don't fuss over the export shape. Each artifact may be a bare row array, the single-cell ARRAY_AGG export exactly as Snowsight hands it to you, or wrapped in {"rows": [...]} — all three parse, and row keys match case-insensitively (Snowflake uppercases identifiers; the parser doesn't care).

Step 2: import

The import endpoint takes all three artifacts in one request. Assemble the body with jq so the file contents are embedded as properly escaped strings:

jq -n --rawfile info information_schema.json \
      --rawfile keys keys.json \
      --rawfile deps object_dependencies.json \
  '{artifacts: {information_schema: $info, keys: $keys, object_dependencies: $deps}}' \
  > import-request.json

curl -sS -X POST \
  "https://coremodels.example.com/graph/integrations/snowflake/import/$PROJECT_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @import-request.json

A successful import answers with counts and two honesty channels:

{ "success": true, "vendor": "snowflake", "projectName": "ANALYTICS",
  "datasetsAdded": 14, "datasetsSkippedExisting": 0, "fieldsAdded": 120,
  "lineageEdgesAdded": 8, "lineageEdgesSkipped": 0, "nodesEnriched": 134,
  "snapshotStored": true, "lossiness": [], "errors": [] }

Three things worth knowing about what just happened:

  • Import is additive. Existing governed nodes are never mutated or deleted. If you re-import after the warehouse changed, already-governed datasets show up in datasetsSkippedExisting — surfacing what changed is the audit's job, and applying a meaning change stays a deliberate human act.
  • lossiness is a success channel, not an error list. It records exactly what the import approximated — for example an exact decimal like NUMBER(38,2) landing as a Double. The exact native type string always survives in the Snowflake Metadata mixin on the node, so nothing is silently forgotten.
  • snapshotStored: true means the parsed estate snapshot was persisted, which is what makes later one-call re-audits possible without fresh extracts.

Step 3: run your first audit

The audit compares fresh artifacts against the governed model and writes nothing. For a first run, re-use the extract you just imported — a clean baseline proves the loop works end to end. Set recordHistory: true so this run starts your rolling drift trail:

jq -n --rawfile info information_schema.json \
  '{artifacts: {information_schema: $info}, recordHistory: true}' > audit-request.json

curl -sS -X POST \
  "https://coremodels.example.com/graph/integrations/snowflake/audit/$PROJECT_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @audit-request.json

Auditing needs only the Viewer role, and by default it is strictly read-only — recording the run in the history is opt-in bookkeeping, which is why recordHistory exists.

Step 4: read the result

The response carries machine-readable counts, individual findings, and a markdown field holding a human-readable report you can paste straight into a pull request. The fields that matter first:

  • errorCount — the hard signal. Greater than zero means the estate violates governed meaning; this is the number a CI gate fails on.
  • warningCount and infoCount — advisory tiers.
  • codes — a map of finding code to occurrence count, so you can see at a glance whether you have one problem thirty times or thirty problems once.
  • findings — the individual records. Each looks like this:
{ "section": "Conformance", "severity": "Warning", "code": "semi-structured-column",
  "subject": "ANALYTICS.PUBLIC.EVENTS.PAYLOAD",
  "message": "VARIANT column carries ungoverned semi-structured data — its inner schema is invisible to consumers and agents." }

Findings fall into three sections. Coverage tells you what exists in the warehouse but isn't governed yet (dataset-unmapped, field-unmapped). Drift tells you where the estate and the governed model disagree (dataset-removed, field-removed, field-type-drift, enum-constraint-removed, enum-narrowed, enum-widened, contract-drift). Conformance carries Snowflake-specific best-practice rules:

CodeSeverityMeaning
semi-structured-columnWarningVARIANT/OBJECT/ARRAY column — its inner schema is ungoverned
key-column-undeclaredWarningan ID/*_ID table column with no declared PK/FK
table-no-commentInfoundocumented table or view

On a first audit of a freshly imported schema, expect errorCount: 0 with, typically, a handful of conformance findings — most estates have a few undocumented tables and at least one VARIANT column. That's the point: the audit tells you where governance is thin before it tells you where it's broken.

Where to go from here

You now have the full loop in miniature: a governed model, a recorded audit run, and a report you can read. Three natural next moves, each a single call: POST .../snowflake/reaudit/$PROJECT_ID with an empty body {} re-checks the stored estate snapshot whenever the governed model changes, with no fresh extract needed; GET .../snowflake/badge/$PROJECT_ID returns an SVG status badge from the latest recorded run; and the machine-to-machine audit route under /v1 turns the same check into a CI gate on schema-change pull requests.

The full Snowflake quickstart in our docs covers those, the DDL generation path back out (CREATE OR REPLACE TABLE from the governed model), and the honest list of what the connector deliberately does not do.