feat(engine): W004 warning for unresolved classification tags#253
Merged
hugocorreia90 merged 1 commit intomainfrom Apr 24, 2026
Merged
Conversation
a1fa54c to
f53c6c5
Compare
…on tags
Emit one Warning per (model, column, tag) triple where a column's
`[classification]` tag has no matching `[mask]` / `[mask.<env>]`
strategy and isn't listed in `[classifications.allow_unmasked]`.
Makes the "I tagged a column but forgot the masking strategy"
silent config error visible at `rocky compile` time instead of
surfacing only when `rocky run` resolves the mask on the warehouse.
A tag T is resolved iff `RockyConfig.mask` contains a top-level
`Strategy(_)` entry keyed by T OR any `EnvOverride(_)` entry whose
inner map contains T — i.e., the check runs against the union of
default + per-env strategies, so a tag declared only under
`[mask.prod]` doesn't warn in dev.
Wiring:
* `CompilerConfig` gains `mask: BTreeMap<String, MaskEntry>` and
`allow_unmasked: Vec<String>`, both defaulted to empty via
`#[derive(Default)]`. Empty defaults = W004 never fires = status
quo, so call sites that don't load a `RockyConfig` (LSP init,
benches, tests, pre-DAG compile in `rocky run`) keep working with
`..Default::default()`.
* `rocky compile` loads `[mask]` + `[classifications.allow_unmasked]`
from rocky.toml (falls back to empty when no config path).
* `rocky run`'s post-DAG governance compile wires the same fields
from the already-loaded `RockyConfig`, so warnings fire even when
someone calls `rocky run` without `rocky compile` first.
* Both `compile_project` and `compile_incremental` call the check —
the LSP's incremental path stays in parity with the full-compile
diagnostic set.
Tests cover: resolved tag → no diagnostic, unresolved tag → W004 with
model/column/tag in the message + both remedies in the suggestion,
allow_unmasked suppression, tag resolved only via `[mask.<env>]`,
two models each carrying their own distinct unresolved tag.
Template: mirrors the `W003` emission shape in typecheck.rs — same
`Diagnostic::warning(CODE, model, message).with_suggestion(...)`
construction, Warning severity, no source span (per-model sidecar
metadata, not SQL location).
f53c6c5 to
ca4a32e
Compare
8 tasks
hugocorreia90
added a commit
that referenced
this pull request
Apr 24, 2026
Governance-waveplan polish wave on top of v1.16.0/v1.12.0/v1.8.0. Engine 1.17.0: - FR-009 BREAKING: reject empty workspace_ids without opt-in (#250) - --env flag on rocky run / rocky plan + plan preview of classification / mask / retention actions (#251) - Wiremock coverage for apply_column_tags + apply_masking_policy (#252) - W004 warning for unresolved classification tags (#253) - SCIM client + per-catalog GRANT for reconcile_role_graph (#254) - rocky retention-status --drift warehouse probe (#255) Dagster 1.13.0 (tracks engine 1.17.0): - Pluggable per-call kwarg resolvers on RockyResource (#248) - Auto-surface compliance + retention-status on RockyComponent (#249) - Pre-flight governance_override validator (#250) - Regenerated PlanResult with env + action preview fields (#251) VS Code 1.9.0 (tracks engine 1.17.0): - Regenerated plan.ts with env + 3 governance-action interfaces (#251)
hugocorreia90
added a commit
that referenced
this pull request
Apr 24, 2026
Governance-waveplan polish wave on top of v1.16.0/v1.12.0/v1.8.0. Engine 1.17.0: - FR-009 BREAKING: reject empty workspace_ids without opt-in (#250) - --env flag on rocky run / rocky plan + plan preview of classification / mask / retention actions (#251) - Wiremock coverage for apply_column_tags + apply_masking_policy (#252) - W004 warning for unresolved classification tags (#253) - SCIM client + per-catalog GRANT for reconcile_role_graph (#254) - rocky retention-status --drift warehouse probe (#255) Dagster 1.13.0 (tracks engine 1.17.0): - Pluggable per-call kwarg resolvers on RockyResource (#248) - Auto-surface compliance + retention-status on RockyComponent (#249) - Pre-flight governance_override validator (#250) - Regenerated PlanResult with env + action preview fields (#251) VS Code 1.9.0 (tracks engine 1.17.0): - Regenerated plan.ts with env + 3 governance-action interfaces (#251) Also refreshes transitive dependencies across all three artifacts: - Cargo.lock: 14 transitive bumps (rustls v0.23.39, tokio v1.52.1, uuid v1.23.1, webpki-roots v1.0.7, compression-codecs v0.4.38, and 9 others) - uv.lock: 10 transitive bumps (pydantic v2.13.3, dagster-pipes/shared v1.13.2, datamodel-code-generator v0.56.1, ruff v0.15.11, and 5 others) - package-lock.json: transitive-only via npm update; direct deps unchanged so the engines.vscode / @types/vscode / test-electron triangle stays in lockstep
hugocorreia90
added a commit
that referenced
this pull request
Apr 24, 2026
Governance-waveplan polish wave on top of v1.16.0/v1.12.0/v1.8.0. Engine 1.17.0: - FR-009 BREAKING: reject empty workspace_ids without opt-in (#250) - --env flag on rocky run / rocky plan + plan preview of classification / mask / retention actions (#251) - Wiremock coverage for apply_column_tags + apply_masking_policy (#252) - W004 warning for unresolved classification tags (#253) - SCIM client + per-catalog GRANT for reconcile_role_graph (#254) - rocky retention-status --drift warehouse probe (#255) Dagster 1.13.0 (tracks engine 1.17.0): - Pluggable per-call kwarg resolvers on RockyResource (#248) - Auto-surface compliance + retention-status on RockyComponent (#249) - Pre-flight governance_override validator (#250) - Regenerated PlanResult with env + action preview fields (#251) VS Code 1.9.0 (tracks engine 1.17.0): - Regenerated plan.ts with env + 3 governance-action interfaces (#251) Also refreshes transitive dependencies across all three artifacts: - Cargo.lock: 14 transitive bumps (rustls v0.23.39, tokio v1.52.1, uuid v1.23.1, webpki-roots v1.0.7, compression-codecs v0.4.38, and 9 others) - uv.lock: 10 transitive bumps (pydantic v2.13.3, dagster-pipes/shared v1.13.2, datamodel-code-generator v0.56.1, ruff v0.15.11, and 5 others) - package-lock.json: transitive-only via npm update; direct deps unchanged so the engines.vscode / @types/vscode / test-electron triangle stays in lockstep
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add a compile-time warning (W004) that fires once per
(model, column, tag)triple where a column's[classification]tag has no matching[mask]/[mask.<env>]strategy and isn't listed in[classifications.allow_unmasked]. Catches the "I tagged a column but forgot the strategy" class of silent config error atrocky compiletime instead of waiting forrocky runto resolve masks on the warehouse. Complements the classification + mask surfaces shipped in #241.Example
Given
rocky.toml:and
models/accounts.toml:rocky compilenow emits:Resolution rule
A tag
Tis resolved iffRockyConfig.maskcontains either:MaskEntry::Strategy(_)entry keyed byT, orMaskEntry::EnvOverride(_)entry whose inner map containsT.In other words, the check runs against the union of default + per-env strategies, so a tag declared only under
[mask.prod]doesn't warn in dev.[classifications.allow_unmasked]is the explicit escape hatch — any tag listed there suppresses W004 without pretending a masking strategy exists.Wiring
CompilerConfiggainsmaskandallow_unmasked, both defaulted empty via#[derive(Default)]. Empty = no-op, so every existing call site (LSP init, benches, tests, pre-DAG compile path inrocky run) keeps working with..Default::default().rocky compileloads the fields fromrocky.toml(falls back to empty when no config path is supplied).rocky run's post-DAG governance compile wires them from the already-loadedRockyConfig, so warnings surface even onrocky runwithout a priorrocky compile.compile_projectandcompile_incrementalcall the check — the LSP's incremental path stays in parity with the full-compile diagnostic set.Template
Mirrors the existing
W003emission pattern intypecheck.rs(Warning severity,Diagnostic::warning(CODE, model, message).with_suggestion(...), no source span — W004 is per-model sidecar metadata, not a SQL location).Tests added (all in
engine/crates/rocky-compiler/src/typecheck.rs)w004_resolved_tag_emits_no_diagnostic— tag mapped by a top-level[mask]entryw004_unresolved_tag_emits_diagnostic_with_helpful_text— message carries model + column + tag; suggestion carries both remediesw004_allow_unmasked_suppresses_diagnostic— tag inallow_unmaskedis silentw004_tag_resolved_only_via_env_override_is_resolved—[mask.prod]-only tag counts as resolvedw004_emits_one_diagnostic_per_model_column_tag— two models each with a distinct unresolved tag yield two W004s attributed to the right modelTest plan
cd engine && cargo test -p rocky-compiler— 249 unit + 15 integration tests pass, including the 5 neww004_*casescd engine && cargo test --workspace— all crate test suites greencd engine && cargo clippy --workspace --all-targets -- -D warnings— cleancd engine && cargo fmt --check— cleanjust codegenleavesgit statusclean (W004 is a pure compiler-layer diagnostic, no CLI output-struct changes, no schemas cascade)🤖 Generated with Claude Code