Encode intent/decision/plan workflow for Snowflake platform delivery so engagements share a durable recipe instead of one-off LLM chats. Co-authored-by: Cursor <cursoragent@cursor.com>
42 KiB
| name | overview | todos | isProject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Snowflake Bootstrap Strategy | Internal blueprint repo (modules, schemas, wizard, policies, skills) plus per-customer repos. Wizard drives greenfield/brownfield intent. Customer env gets repo+CI+secrets+Snowflake automation user; blueprint IP stays pinned on operator side by default. |
|
false |
Snowflake Platform Tool: Layered Design (Revised)
Corrections to prior framing
Two important clarifications from you:
- Session N+1 is not empty. You pass Terraform state (and related IaC), so the LLM can reconstruct what exists. What does not travel with state is why a choice was made (rationale, rejected alternatives, temporary compromises, client constraints).
- “LLM-driven” ≠ “LLM owns the work.” Execution and ongoing management are already (or should remain) IaC / tools. The LLM is how developers drive generation and evolution of that code today — with weak durable intent/decision memory between sessions.
Prior plan over-indexed on “empty context” and under-specified the real stack. This revision treats the problem as a multi-layer system, not a single bootstrapper slogan.
Precise problem statement
| What you already have (or can pass in) | What decays between sessions |
|---|---|
| TF state / config (what is managed) | Decision rationale (why this shape) |
| Live Snowflake (what exists) | Intent vs exception (is this deviation permanent?) |
| LLM + developer loop (how changes get authored) | Stable operating procedures as bound skills over a schema |
| Best practices in people’s heads / docs | Machine-checkable policy + linked ADRs |
So the product is not “replace Terraform.” It is:
an intent + decision + policy layer on top of IaC, with LLM as a skilled driver that must read/write those layers — not only TF state.
Layer stack (this is the real work)
flowchart TB
subgraph L7 [L7_OperatorUX]
chat[LLM_Chat_Skills]
cli[CLI_PR_Workflow]
end
subgraph L6 [L6_PolicyVerify]
policy[PolicyAsCode]
verify[PostApplyAssertions]
end
subgraph L5 [L5_PlannerCodegen]
planner[IntentDelta_to_TFPlan]
codegen[ModuleAware_Codegen]
end
subgraph L4 [L4_DecisionKnowledge]
adr[DecisionRecords]
skills[OpsSkills_BestPractices]
end
subgraph L3 [L3_IntentModel]
blueprint[Blueprint_vN]
intent[CustomerIntent]
overrides[Overrides]
end
subgraph L2 [L2_Observe]
tfstate[TerraformState]
inventory[LiveSnowflakeInventory]
drift[DriftClassifier]
end
subgraph L1 [L1_Execute]
tf[Terraform_Apply]
snow[SnowflakeAccount]
end
chat --> intent
chat --> adr
chat --> planner
cli --> planner
skills --> chat
blueprint --> intent
overrides --> intent
intent --> planner
adr --> planner
tfstate --> drift
inventory --> drift
drift --> planner
planner --> codegen --> tf --> snow
tf --> tfstate
snow --> inventory
policy --> planner
verify --> snow
Each layer has its own schema, APIs, and failure modes. They should be designed deliberately — not collapsed into “a bootstrap tool.”
L1 — Execution (already largely exists)
Owns: applying changes to Snowflake
Artifacts: Terraform modules/resources, providers, backends, CI apply
Non-goals: remembering why; encoding client intent in a portable form
Assumption going forward: Terraform remains the executor (you already pass TF state into sessions). Other engines only matter if you later choose them; do not reopen that as the center of the product.
L2 — Observe / reconcile
Owns: truthful picture of managed vs live vs drifted
Inputs:
- Terraform state + config
- Live account inventory (SHOW / INFORMATION_SCHEMA / account usage as needed)
Outputs:
- Resource inventory joined to TF addresses
- Drift classes:
in_sync,drifted_managed,unmanaged_live,in_state_missing_live - Adoption candidates for brownfield
This layer answers “where things are.” You already approximate it by feeding TF state to the LLM; inventoriing live Snowflake closes blind spots TF does not see (manual grants, console clicks, out-of-band objects).
L3 — Intent model (above raw TF)
Owns: what we mean to run for this customer, parameterized
Artifacts (illustrative):
blueprint.lock— pinned platform blueprint versionintent.yaml— domains, envs, data products, role assignments, warehouse profilesoverrides.yaml— explicit deviations from blueprint defaults
Raw TF is a compiled artifact of intent. Today the LLM often edits TF directly; that works, but intent stays implicit inside HCL. Making intent explicit is what makes “tweak per client” repeatable without re-deriving the whole graph from chat.
Deviations live here as data, not as undocumented HCL quirks.
L4 — Decision / knowledge (the real N+1 gap)
Owns: why — durable across sessions even when TF state is provided
Artifacts:
- Decision records linked to resources or intent keys, e.g.:
decision_id,applies_to(TF address or intent path),status(active/superseded),rationale,alternatives_rejected,expires_on,client_constraint
- Curated ops skills / best-practice packs (versioned markdown/YAML the LLM must load)
- Mapping: blueprint rule → allowed override types → required decision fields
Example of what TF state cannot tell the next session:
- “We kept a flat role for finance because their IdP groups land weekly; revisit after SCIM.”
- “Prod shares DEV database naming temporarily for migration; remove after cutover date.”
- “Blueprint says account-per-env; this client insisted on database-per-env for cost.”
Without L4, every N+1 session can rebuild structure from state and still re-litigate or contradict prior judgment.
L5 — Planner / codegen
Owns: turning intent deltas (+ decisions) into concrete IaC changes
Flow:
- Load blueprint + intent + overrides + decisions
- Load observe/drift report
- Compute target module graph / resource set
- Emit TF changes (or a plan preview) — schema-validated
- Attach/update decision records for any override touched
LLM role here: assist codegen and explain diffs inside this pipeline, not free-form “write some Snowflake SQL.” Guardrails: module allowlist, forbidden patterns, required decision when overriding policy.
L6 — Policy / verify
Owns: machine-checkable best practices (not only prose skills)
Examples:
- No
ACCOUNTADMINfor service users - Raw zone writable only by loader roles
- Env isolation invariants
- Naming / tagging requirements
- Override must reference an active decision record
Runs on: pre-plan validate, PR checks, post-apply assertions.
Skills (L4) teach the LLM; policy (L6) fails the build when the LLM or a human skips the rules.
L7 — Operator UX
Owns: how developers drive the system
- CLI:
discover,intent validate,plan,apply,drift,decision add - LLM skills: load L3+L4+L2 context automatically; propose intent/override/decision patches; call planner
- PR workflow: intent/decision/TF in one reviewable unit
This is where “LLM-driven” correctly lives — as the driver, with IaC as the engine.
What “repeatability with deviation” means in this stack
Blueprint_vN
+ Customer intent (params)
+ Overrides (structured deviations)
+ Decisions (why those overrides exist)
→ Planner → Terraform → Snowflake
→ Observe → Drift → next change
Same steps, different inputs. Not the same HCL pasted every time; not a blank-slate LLM redesign every time either.
Implementation breadth (why this feels “many layers”)
Rough build slices (can be separate workstreams):
- Customer project layout — conventions for TF root, intent, decisions, skills pin
- Blueprint modules — your recommended infra as versioned TF modules (not one-off roots)
- Intent schema + compiler — intent → module inputs (deterministic first)
- Decision store format + linking — resource/intent annotations
- Discover/drift — state + live inventory join
- Policy pack — OPA/Conftest/custom validators on intent+TF
- LLM skill pack — Cursor/agent skills that require reading L2–L4 before editing
- Brownfield adopt — import live → intent proposal → unmanaged list
- Verification suite — Snowflake-side integration checks per customer
- Multi-customer packaging — how blueprints version and how clients pin upgrades
This is product/platform engineering, not a weekend script. The earlier “bootstrapper” framing was the tip of that iceberg.
Recommended discussion order (next deep-dives)
Do not design all layers at equal depth at once. Suggested sequence:
- L3 Intent model — what must be expressible without dropping to raw TF
- L4 Decision store — schema that captures the why you lose today
- L5 Planner contract — how intent patches become module-safe TF diffs
- L2 Observe — drift model given you already use TF state
- L6 Policy — which best practices become hard gates first
- L7 Skills — only after schemas exist so skills have something binding to edit
Verdict (revised)
- You are already closer than “LLM owns infra”: IaC owns execution; LLM drives authoring.
- Passing TF state into session N+1 solves topology memory, not decision memory.
- The useful internal product is a layered intent/decision/policy system over Terraform, with discover/drift and LLM skills as interfaces — not a single high-level “bootstrap tool” narrative.
- Next value is to deep-dive one layer at a time and nail schemas/contracts before writing a lot of code.
Blueprint repo — what lives there
The blueprint repo is your IP / product core. Customer repos consume a pinned version of it; they do not fork a copy of everything.
snowflake-platform-blueprint/
modules/ # Terraform modules (executor building blocks)
environment/ # env isolation pattern
database_zones/ # landing/raw/curated/marts (or your real zones)
rbac/ # role hierarchy + grant patterns
warehouses/
service_principals/
monitoring/ # resource monitors, basic alerts stubs
schemas/
intent.schema.json # what intent.yaml must look like
decision.schema.json
discover-report.schema.json
defaults/
intent.greenfield.yaml # starter intent
intent.brownfield.yaml
warehouse_profiles.yaml
access_profiles.yaml # e.g. standard vs restricted
wizard/
catalog.greenfield.yaml # ordered questions, branching, defaults
catalog.brownfield.yaml # starts with discover-driven questions
mapping.yaml # answer_id → intent path / override / decision prompt
policies/ # hard gates (Conftest/OPA/custom)
no_accountadmin_services.rego
env_isolation.rego
override_requires_decision.rego
skills/ # LLM operator packs (Cursor/agent skills)
platform-ops/SKILL.md
rbac-best-practices.md
brownfield-adopt.md
templates/
customer-repo/ # skeleton copied by `init`
intent.yaml
decisions/.gitkeep
terraform/
.github/workflows/ # optional CI template for customer
tests/
reference-customer/ # golden intent → expected plan fixtures
CHANGELOG.md # blueprint v1, v2, …
VERSION
Rule of thumb
| In blueprint repo | In customer repo |
|---|---|
| Modules, schemas, wizard catalogs, policies, skills, defaults | Filled intent.yaml, decisions/, TF root that calls modules, state backend config, connection secrets refs |
Versioned releases (v3.2.0) |
blueprint: v3.2.0 pin |
| Your best-practice brain | This client’s answers and exceptions |
Wizard — yes, first-class
The wizard is the primary greenfield/brownfield onboarding UX. It is not free-form chat inventing infra; it is a question catalog over the blueprint that writes intent.yaml + prompts for decisions/ when answers deviate from defaults.
Shape
flowchart LR
mode{Greenfield_or_Brownfield}
discover[DiscoverLiveAccount]
ask[WizardQuestionCatalog]
intent[Write_intent.yaml]
decide[PromptDecisionIfOverride]
plan[platformctl_plan]
apply[ApplyInAllowedContext]
mode -->|greenfield| ask
mode -->|brownfield| discover --> ask
ask --> intent --> decide --> plan --> apply
Greenfield wizard (examples)
Ordered questions from wizard/catalog.greenfield.yaml:
- Customer name / account identifiers
- Env model: account-per-env vs db-per-env (default from blueprint)
- Domains / data products (repeatable)
- Access profile per domain (standard / restricted)
- Warehouse profile
- Identity: SSO now / later / service users only
- Anything that contradicts a default → forced decision capture (rationale + optional expiry)
Answers compile via wizard/mapping.yaml → intent.yaml.
CLI shape:
platformctl wizard --customer acme --mode greenfield
# or non-interactive from a saved answers file:
platformctl wizard --answers acme-answers.yaml
LLM can run the same catalog conversationally (“ask next unanswered question”), but the catalog is the contract — same outputs whether human CLI or chat.
Brownfield wizard
- Connect +
discover - Show match % / unmanaged / conflicts
- Questions become adoption choices: manage / ignore / migrate-later per object group
- Propose intent draft from what matched
- Require decisions for keep-legacy and naming conflicts
adopt→ import selected into TF → plan residual gaps
Same wizard engine; different catalog + discover as step 0.
Bringing this into a new customer environment
Split operator toolkit (yours) from runtime footprint (what must exist in the customer’s world).
Default delivery model (recommended)
You keep the blueprint + wizard. The customer receives a customer project that can plan/apply against their Snowflake, usually from their Git + CI — without needing your full blueprint source if modules are consumed as a versioned package (git tag, private module registry, or vendored lock).
flowchart TB
subgraph your_side [YourSide_Operator]
blueprint[BlueprintRepo]
wizard[Wizard_CLI_or_Chat]
end
subgraph customer_side [CustomerEnvironment]
crepo[CustomerGitRepo]
ci[CustomerCI]
tfstate[TFStateBackend]
sf[SnowflakeAccount]
end
wizard -->|generates_or_updates| crepo
blueprint -->|pinned_modules| crepo
crepo --> ci --> tfstate
ci -->|terraform_apply| sf
What must be set up once in the customer environment
- Snowflake bootstrap identity — service user + key pair (or OAuth), roles enough for Terraform (often a dedicated automation role; initial ACCOUNTADMIN bootstrap is one-time manual)
- Secrets — private key / cloud creds in customer secret store (GitHub Actions secrets, Vault, etc.) — never in git
- TF state backend — customer-controlled (S3/Azure/GCS + lock), or your org’s backend if you operate the account under contract
- Customer git repo — the generated project from
init/ wizard - CI workflow — plan on PR, apply on merge/approval (template from blueprint
templates/customer-repo)
What does not need to live in the customer env
- Wizard question catalogs (can run on your laptop during engagement)
- Full policy authoring / skill authoring
- Blueprint module source (if you publish versioned modules they consume read-only)
How day-1 “bring-up” works in practice
- Customer creates (or you create) empty Snowflake account / grants you access
- One-time manual: create Terraform service user + store key in their secrets
- You run wizard (locally or internal portal) → produces customer repo contents
- Push customer repo to their GitHub/GitLab (or your managed repo if contract says so)
- Wire secrets + state backend
- CI runs
plan/applyinto their account - Ongoing changes: PR to customer repo (intent/decision/TF); wizard/LLM used by your team as operators
Alternate operating modes (when needed)
| Mode | When | Footprint in customer |
|---|---|---|
| A. Customer-hosted CI (default) | Normal consulting delivery, customer owns account | Repo + secrets + state + Snowflake service user |
| B. You operate apply | You manage platform under retainer | Same Snowflake identity; state/CI may stay in your org |
| C. Fully offline customer | No external module fetch | Vendor/pin blueprint modules into customer repo at release time |
Avoid shipping the entire blueprint monorepo into every customer unless they require air-gapped ownership — that blurs IP and versioning.
“Tool in customer environment” checklist (handoff)
- Snowflake automation user created
- Secrets installed in CI
- State backend ready
- Customer repo pushed with blueprint pin
- First successful
planin CI - First successful
apply - Drift job scheduled or documented
- Runbook: how to add a domain / raise a decision / upgrade blueprint pin
CLI access model and packaging
Will the CLI reach into “my” repo from the customer environment?
No — not if designed correctly. That would be brittle (VPN, private git auth, IP leakage) and is the wrong dependency direction.
Correct dependency direction:
Published artifacts (versioned)
├── platformctl (CLI package on private PyPI / uv tool)
└── blueprint modules + schemas + wizard catalogs (versioned release, e.g. v3.2.0)
Customer environment / CI only needs:
├── customer git repo (intent, decisions, TF root)
├── platformctl (optional; CI may only need terraform)
└── ability to fetch pinned blueprint vX (registry / git tag / vendored copy)
The CLI works against the local customer project directory (and Snowflake + TF state). It does not SSH into your laptop or clone your live blueprint working tree.
| Who runs what | Accesses |
|---|---|
| You on laptop (wizard during engagement) | Local checkout or cached blueprint package + writes customer repo |
Customer CI terraform plan/apply |
Customer repo + pinned TF modules only (often no platformctl) |
| Customer engineer with platformctl (optional) | Same: local customer repo + installed CLI + pinned blueprint package from registry |
If the customer is air-gapped: vendor blueprint v3.2.0 into the customer repo at release time (mode C). Still no live link to your private monorepo.
Packaging recommendation
Default: Python package, installed via pipx or uv tool install.
Why Python over npm/Go for this tool:
- Snowflake ecosystem is Python-native (
snowflake-connector-python, many internal data-platform scripts) - Excellent YAML/JSON schema, Jinja, Click/Typer CLIs
- Easy private publish (Azure Artifacts, AWS CodeArtifact, GitHub Packages, private PyPI, or
pip install git+https://…) pipx/uvxgives an isolated CLI without polluting customer project venvs
| Option | Fit | Use when |
|---|---|---|
| Python + pipx/uv (recommended) | Best overall | Your team already does Snowflake/Python |
| Go single binary | Best “download one file” DX | Strict customer lockdown, no Python allowed on jump hosts |
| npm | Weak fit | Only if your org is JS-only; TF/Snowflake glue will feel awkward |
| Snowflake CLI plugin | Niche | If you standardize on snow CLI everywhere; still thin wrapper over same Python core |
| Docker image | Good CI companion | ghcr.io/yourorg/platformctl:3.2.0 for customer pipelines without local install |
Suggested package split:
platformctl— CLI (wizard, validate, plan wrapper, decision helpers, discover)platform-blueprint(or git-tagged module repo) — versioned content the CLI resolves byblueprint: v3.2.0in intent
Publish both to your private registry. Customer CI authenticates to that registry or you vendor the blueprint pin into their repo so they need zero access to your org.
Minimal customer footprint (preferred)
Many engagements should land as:
- Customer repo + Terraform + CI
- No requirement that customer installs platformctl
You use platformctl/wizard on your side to generate PRs; their CI only runs terraform. Install platformctl in customer env only if their engineers will run discover/wizard themselves.
After apply: incomplete intake, late changes, edit vs teardown
Requirements are never complete on day one. The wizard is not a one-shot form you fill once and throw away. It is a re-entrant way to change intent over the life of the customer project.
Mental model
Apply creates managed state
→ later: change intent / decisions (normal path)
→ plan shows create / update / destroy for the delta only
→ apply the delta
Terraform already knows how to add, change, and remove resources from a new desired config. The product’s job is to make those intent edits safe and explainable — not to re-run “full setup from scratch.”
What you do when something is added or changed later
| Change type | Recourse | Typical commands |
|---|---|---|
| Add domain / env / warehouse profile | Edit intent → plan → apply | wizard (partial) or hand-edit intent.yaml |
| Tighten/loosen access | Edit intent + maybe new decision → plan → apply | same |
| Reverse a temporary exception | Update/expire decision, change override → plan → apply | may destroy specific grants/objects |
| Drop a domain / decommission env | Edit intent (remove) → plan shows destroys → apply with review | selective teardown via TF |
| Wrong blueprint choice early on | Change intent params or bump blueprint pin → plan (can be large) | prefer migrate path over wipe |
| Abandoned experiment / full reset | Rare teardown of a managed stack or env | platformctl destroy --target … with guards |
Default recourse after apply = edit + plan + apply.
Teardown = explicit, scoped destroy, not the normal way to “fix a mistake.”
How the wizard helps when intake is partial
- Partial / resume — Wizard tracks answered vs unanswered; you can run it again weeks later and only fill new questions (e.g. “SSO ready now?”, “Add marketing domain?”).
- Change commands, not only init — Same catalog supports intents like:
wizard --resumewizard add domainwizard set identity ssowizard adopt(brownfield leftovers)
- Diff-aware — After answers, it patches
intent.yaml/decisions/and runsplanso you see only the delta against what’s already applied. - Decisions for late surprises — When a late requirement breaks a default, wizard forces a decision record (rationale + optional expiry) instead of a silent HCL tweak.
- Drift before change —
driftfirst if humans may have clicked in the Snowflake UI; reconcile, then edit intent.
So wizard = guided editor of intent over time, not a day-0 questionnaire.
Edit path (happy path for “things changed”)
Day 0: intent has 2 domains → apply
Day 45: business wants finance + SSO
→ wizard resume / edit intent
→ new decision if needed
→ plan: +finance objects, +SSO stubs, maybe grant changes
→ apply
Day 90: remove temporary finance prod-read
→ expire decision, remove override
→ plan: revoke grants / drop exception role mapping
→ apply
No full rebuild. State and decisions carry forward.
Teardown path (when you actually need destroy)
Use sparingly, always scoped:
- Resource-level: remove from intent → TF plans
destroyfor those addresses only - Env-level:
platformctl destroy --env dev(wrapper around TF destroy targeting that module) - Customer-level: last resort; requires explicit confirmation + maybe separate break-glass role
Guards:
- Policy: destroy of prod requires extra approval / decision
- Refusing destroy when unmanaged dependents exist (warn from discover)
- Prefer “quarantine” (revoke access, keep data) over drop for data-bearing schemas
What not to do
- Don’t re-run greenfield
initon an existing customer to “pick up new requirements” - Don’t tear down prod to apply a naming tweak — migrate with plan
- Don’t use the LLM to emit ad-hoc DROP scripts outside the intent→plan loop
Short answer
After something is applied, the recourse is almost always edit (intent/decisions) → plan → apply.
Wizard helps by making those later edits guided and resumable.
Teardown is the exception — selective destroy when you intentionally remove a domain/env/stack, still driven by intent and Terraform, not a separate wipe tool as the main workflow.
Reducing adoption friction (Snowflake people, not “platformctl experts”)
The tool is useful only if a Snowflake developer/devops/admin can use it without treating it as a second career. Friction reduction is a first-class design goal, not docs afterthought.
Design principle
Speak Snowflake; hide the toolkit.
Users should think in domains, roles, warehouses, envs — not in blueprint schema versions and planner internals.
Friction reducers (concrete)
-
Tiny command surface — teach 5 verbs, not a platform:
wizard/add/plan/apply/drift
Everything else is advanced (decision,adopt,destroy,blueprint upgrade).
-
Snowflake vocabulary in the wizard — questions use their words (“Who can read curated in prod?”), not ours (“Set access_profile on domain intent key”). Mapping to intent is invisible.
-
LLM/Cursor skill as the default front door — for many users the “training” is:
- Open customer repo
- Say: “Add a finance domain with restricted prod access”
- Skill runs validate → plan → shows TF/Snowflake diff in plain language
They never memorize flags. CLI remains for CI and power users.
-
Show artifacts they already trust — every plan prints:
- Human summary (“creates 2 roles, 1 warehouse, grants SELECT on FINANCE_CURATED”)
- Familiar
terraform plan - Optional SQL preview
No proprietary plan language to learn.
-
Golden-path recipes, not a manual — short how-tos aligned to jobs:
- How to add a domain
- How to grant analyst read
- How to add an env
- How to fix drift
Each is 5–8 steps. Persona = Snowflake admin, not “platformctl operator.”
-
Sensible defaults everywhere — first
wizardrun should succeed with mostly Enter/defaults. Customization is opt-in; decisions only when they break a default. -
Don’t force the tool into daily CI life — customer pipelines keep running plain Terraform. Only operators who change intent need wizard/CLI/skill. Admins who only approve PRs review the same TF diff they already know.
-
Escape hatch without shame — advanced users can edit Terraform modules directly; next
drift/planstill works. The tool must not punish leaving the happy path (document “supported vs at-your-own-risk” edits). -
In-repo onboarding, zero slide deck required:
platformctl doctor— checks auth, backend, blueprint pin, suggests next command- Customer repo
READMEauto-generated: “Your next step is …” platformctl explain— why this grant exists (reads decisions)
-
Progressive disclosure — day 1: wizard + apply. Week 4: decisions. Month 3: blueprint upgrade / adopt. Don’t dump the layer cake in training.
What “trained” should mean (target)
| Role | Must learn | Need not learn |
|---|---|---|
| Snowflake admin | Approve plan summaries; answer wizard in Snowflake terms | Blueprint internals, policy engine |
| DevOps | Wire secrets/state once; TF plan/apply in CI | Wizard catalogs, intent schema |
| Platform operator (you) | Intent, decisions, blueprint pins | — (this is the deep skill) |
If everyone must learn what only operators need, friction has failed.
Anti-patterns that create overhead
- Requiring a multi-day course before first apply
- Inventing new jargon (
intent compiler,L3 model) in the UI - Forcing platformctl in every customer engineer’s toolchain
- Plans that only make sense if you know module graph internals
- Blocking work when someone hand-fixed one grant in Snowflake (prefer detect + guided reconcile)
Success metric
A Snowflake-competent person who has never seen the tool completes “add a domain to an existing customer” in one sitting with only the skill/README — no workshop.
How skills / LLM connect to the tool
Skills are not a second system. They are an optional front door onto the same customer repo + platformctl control plane. The LLM does not apply Snowflake changes by itself; it drives the same files and commands a human would.
Connection in one picture
flowchart TB
user[User_in_Cursor_or_chat]
skill[Skill_pack_from_blueprint]
ctx[Load_customer_context]
llm[LLM_reasoning]
files[Edit_intent_decisions]
cli[platformctl_validate_plan_drift]
tf[Terraform_apply]
sf[Snowflake]
user --> skill
skill --> ctx
ctx --> llm
skill --> llm
llm -->|"propose_or_write"| files
llm -->|"invoke"| cli
files --> cli
cli --> tf --> sf
| Piece | Role |
|---|---|
Blueprint skills/ |
Versioned instructions + best-practice text the agent must load (how we do RBAC, when to require a decision, forbidden patterns) |
| Customer repo | Durable context: intent.yaml, decisions/, TF state ref, last plan — replaces “empty chat memory” |
| LLM | Interprets natural language; maps Snowflake-ish requests onto intent edits / wizard answers / CLI calls |
platformctl |
Source of truth for validate / plan / drift / apply wrappers — deterministic |
| Terraform | Still the only applier to Snowflake |
What a skill actually contains
Shipped inside the blueprint package (pinned with the customer’s blueprint version), e.g.:
skills/platform-ops/
SKILL.md # when to use, workflow, allowed tools
rbac-best-practices.md # your standards (prose the model follows)
examples.md # few-shot: “add domain” → intent diff
SKILL.md tells the agent roughly:
- Open / read
intent.yaml,decisions/, blueprint pin - Prefer
platformctlover inventing SQL/HCL - If the request breaks a default → create/update a decision
- Always run
platformctl planbefore suggesting apply - Never apply without explicit user approval
Same catalog the CLI wizard uses can be referenced so chat and CLI stay aligned.
What the user experiences
Without skill (CLI-only):
platformctl wizard add domain
platformctl plan
platformctl apply
With skill (LLM front door):
“Add a finance domain with restricted prod curated read for month-end; revisit after SSO.”
Agent:
- Loads skill + customer intent/decisions
- Patches
intent.yaml(add finance, restricted profile) - Writes
decisions/…-finance-prod-readwith rationale + expiry - Runs
platformctl validate+platformctl plan - Shows summary + terraform plan
- Waits for you to say apply →
platformctl apply(or CI PR)
Same end state as CLI; less tool training.
What the LLM is not connected as
- Not a free-form Snowflake admin that emits
GRANTscripts as the system of record - Not the store of decisions (files are)
- Not required in customer CI (CI runs terraform; skills stay in operator IDE)
- Not allowed to skip policy gates —
platformctl planstill fails if override lacks decision
Why this fixes today’s LLM-driven workflow
| Today | With this connection |
|---|---|
| Chat + TF state; why lives in scrollback | Chat + skill + intent/decisions files |
| Each session re-derives process | Skill encodes process; blueprint version pins it |
| LLM may invent one-off HCL/SQL | LLM edits intent; planner/TF emit HCL |
| Best practices in people’s heads | Best practices in versioned skills/ + policies/ |
Where skills live vs where they run
| Artifact | Lives in | Runs in |
|---|---|---|
| Skill packs | Blueprint package skills/ |
Operator IDE (Cursor) or chat host |
platformctl |
pipx/uv install | Laptop or CI (optional) |
| Apply | Terraform | Customer CI / operator with creds |
Customer engineers who only approve PRs never need the skill. Operators who change platforms use skill or CLI — both write the same repo.
Short definition
Skills/LLM = natural-language driver for platformctl + customer intent/decision files, constrained by blueprint skill packs and policies. Connection point is the customer repo and CLI — not a parallel AI path into Snowflake.
Where you type that sentence (Cursor / Claude Code / etc.)
Yes — the natural-language line is typed in an agent coding tool such as Cursor Agent or Claude Code (or similar), with the customer project repo open as the workspace. It is not a separate SaaS chat bound to Snowflake.
Do users need to hunt for skill files?
Ideally no. Skills are installed where the agent auto-discovers them. The user just opens the customer repo and asks in plain language. Looking up SKILL.md paths is for authors/operators maintaining the blueprint — not for every Snowflake admin.
Recommended layout (Cursor)
| Skill placement | Path | Who gets it |
|---|---|---|
| Project skill (preferred for delivery) | customers/acme/.cursor/skills/platform-ops/SKILL.md |
Anyone who clones the customer repo in Cursor |
| Personal skill (your laptop) | ~/.cursor/skills/platform-ops/ |
Only you, across projects |
platformctl init / wizard should copy or link the blueprint’s skills/platform-ops into the customer repo’s .cursor/skills/ so discovery is automatic. Pin the skill content to the same blueprint version as intent.yaml.
Claude Code: same idea — ship the skill/instructions into the repo (e.g. project skill / CLAUDE.md pointer) so opening the repo is enough; exact folder conventions follow that tool’s docs.
Where to run
| Requirement | Why |
|---|---|
| Workspace root = customer repo | Agent sees intent.yaml, decisions/, Terraform |
platformctl on PATH (operator machine) |
Skill invokes validate/plan instead of inventing SQL |
| Snowflake/TF creds as you already use for that customer | Apply/plan still use normal auth — skill doesn’t replace that |
| Network to private blueprint registry only if modules aren’t vendored | Same as non-LLM workflow |
Users do not need to open the blueprint monorepo to chat. They open Acme’s customer repo and talk.
Minimal user checklist
- Clone/open customer repo in Cursor (or Claude Code)
- Ensure project skill is present (shipped by
init; if missing,platformctl skills install) - Ask: “Add finance with restricted prod curated read…”
- Review plan → approve apply / PR
No manual “find and @-mention the skill file” in the happy path — the description/frontmatter should make the agent pick it up when the request matches (platform change, domain, RBAC, etc.). Power users can still @ the skill if the host supports explicit attach.
What if someone doesn’t use Cursor?
They use the CLI wizard only — same repo, same outcomes, no skill files involved. Skills are an accelerator for agent IDEs, not a hard dependency of the platform tool.
End-product visualization: new customer scenario
What you actually open day-to-day
Not a magical Snowflake UI. An internal toolkit that looks like:
- A versioned blueprint repo (your standard platform modules + policies + skills)
- Per customer: a customer project repo containing:
- Terraform (executor)
intent.yaml(what this customer should look like)decisions/(why deviations exist)- generated/updated TF from the planner
- A CLI (and optionally Cursor skills) you run:
init,discover,plan,apply,drift,decision
The LLM is how you talk to that toolkit — it does not replace Terraform.
Scenario A — Mostly greenfield customer (“Acme”)
Day 0 — Kickoff facts you collect (30–60 min)
You learn: 3 domains (sales, finance, ops), need dev/test/prod, Okta SSO coming later, finance wants tighter access, they already created an empty Snowflake account.
Step 1 — Create customer project
platformctl init acme --blueprint v3
Creates repo/folder:
customers/acme/
intent.yaml # mostly empty template
decisions/
terraform/ # wired to blueprint modules
README.md
Step 2 — Fill intent (you or LLM interview)
You (or chat skill) produce something like:
customer: acme
blueprint: v3
environments: [dev, test, prod]
domains:
- name: sales
- name: finance
access_profile: restricted
- name: ops
warehouses:
profile: standard_cost_saver
identity:
sso: planned # not ready yet
Step 3 — Capture a decision where they deviate
Finance insists analysts can see prod curated early (against your default).
platformctl decision add \
--applies-to domains.finance \
--rationale "Business needs prod curated read for month-end; revisit after SSO" \
--expires 2026-10-01
That writes decisions/2026-07-15-finance-prod-read.md (or YAML) linked to the intent override — this is what future-you needed and TF state never had.
Step 4 — Plan (deterministic + optional LLM explanation)
platformctl plan
Tool expands blueprint + intent + decisions → Terraform changes:
- env databases/schemas/zones
- role hierarchy + grants
- warehouses
- service users stubs
- finance override applied only where decision allows
You get a normal terraform plan style diff + a human summary. Policy checks fail if you skipped a required decision for an override.
Step 5 — Apply
platformctl apply # wraps terraform apply with your CI/approvals
Snowflake now matches the plan. TF state is updated as usual.
Step 6 — Week 3 follow-up (new chat session)
Someone says: “Add a marketing domain and tighten finance again.”
New LLM session loads customer repo (intent + decisions + TF state), not tribal memory.
platformctl drift # confirms account still matches managed intent
# edit intent: add marketing
platformctl plan
platformctl apply
The agent can explain: finance prod-read is still an active dated decision, not invent a new story.
Scenario B — Brownfield customer (“Globex”) already has stuff
Day 0 — They have databases, messy roles, some warehouses, half-manual grants.
Step 1 — Init + discover
platformctl init globex --blueprint v3
platformctl discover --connection globex-prod
Output (conceptual):
Found 12 databases, 40 roles, 8 warehouses
Matched to blueprint patterns: 35%
Unmanaged live objects: 62
Conflicts: 4 (naming / dual grant paths)
Step 2 — Adopt, don’t rebuild
Tool proposes an intent.yaml draft + unmanaged.yaml for things you won’t own yet.
You choose:
- Manage: raw/curated zones for 2 domains, new RBAC hierarchy going forward
- Leave unmanaged: legacy
FINANCE_OLDrole until migration date - Record decision: why legacy role stays
Step 3 — Import into Terraform
platformctl adopt --approve
Imports selected objects into TF state / modules so going forward changes are planned, not hand-SQL.
Step 4 — Ongoing
Same as Acme: intent edits → plan → apply → drift. Brownfield just started with discover/adopt instead of empty init.
Scenario C — What today feels like vs what this product changes
| Moment | Today (LLM + TF) | With this product |
|---|---|---|
| New customer start | Chat invents structure; copy patterns from last engagement | init + blueprint + intent template |
| Why finance is special | In someone’s head / old chat | decisions/ file loaded every session |
| Follow-up in 2 months | Pass TF state; re-derive intent from HCL | Pass repo; intent+decisions are explicit |
| Client deviation | One-off HCL tweak | Override + required decision + policy gate |
| Brownfield | Manual inventory in chat | discover → adopt/unmanaged split |
| Execution | Terraform | Still Terraform |
One-sentence product definition
An internal CLI + customer-repo convention on top of your Terraform blueprint that stores customer intent and decisions, plans/applies through IaC, discovers brownfield drift, and lets an LLM drive those files instead of reinventing the platform each chat.