# Snowflake — Quickstart: 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`:

```sql
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`:

```sql
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:

```sql
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:

```bash
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:

```json
{ "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:

```bash
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:

```json
{ "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:

| Code | Severity | Meaning |
|---|---|---|
| `semi-structured-column` | Warning | VARIANT/OBJECT/ARRAY column - its inner schema is ungoverned |
| `key-column-undeclared` | Warning | an `ID`/`*_ID` table column with no declared PK/FK |
| `table-no-comment` | Info | undocumented 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.
