Skip to content

feat(engine): W004 warning for unresolved classification tags#253

Merged
hugocorreia90 merged 1 commit intomainfrom
feat/compiler-w004-unresolved-classification-tags
Apr 24, 2026
Merged

feat(engine): W004 warning for unresolved classification tags#253
hugocorreia90 merged 1 commit intomainfrom
feat/compiler-w004-unresolved-classification-tags

Conversation

@hugocorreia90
Copy link
Copy Markdown
Contributor

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 at rocky compile time instead of waiting for rocky run to resolve masks on the warehouse. Complements the classification + mask surfaces shipped in #241.

Example

Given rocky.toml:

[mask]
pii = "hash"

and models/accounts.toml:

[classification]
owner_email = "pii"
audit_note = "audit_only"   # no [mask.audit_only] anywhere

rocky compile now emits:

warning[W004]: classification tag 'audit_only' on column 'audit_note' has no matching `[mask]` strategy
 = help: add `[mask.audit_only]` to rocky.toml or list `audit_only` in `[classifications.allow_unmasked]`

Resolution rule

A tag T is resolved iff RockyConfig.mask contains either:

  • a top-level MaskEntry::Strategy(_) entry keyed by T, or
  • any MaskEntry::EnvOverride(_) entry whose inner map contains T.

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

  • CompilerConfig gains mask and allow_unmasked, both defaulted empty via #[derive(Default)]. Empty = no-op, so every existing call site (LSP init, benches, tests, pre-DAG compile path in rocky run) keeps working with ..Default::default().
  • rocky compile loads the fields from rocky.toml (falls back to empty when no config path is supplied).
  • rocky run's post-DAG governance compile wires them from the already-loaded RockyConfig, so warnings surface even on rocky run without a prior rocky compile.
  • Both compile_project and compile_incremental call the check — the LSP's incremental path stays in parity with the full-compile diagnostic set.

Template

Mirrors the existing W003 emission pattern in typecheck.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] entry
  • w004_unresolved_tag_emits_diagnostic_with_helpful_text — message carries model + column + tag; suggestion carries both remedies
  • w004_allow_unmasked_suppresses_diagnostic — tag in allow_unmasked is silent
  • w004_tag_resolved_only_via_env_override_is_resolved[mask.prod]-only tag counts as resolved
  • w004_emits_one_diagnostic_per_model_column_tag — two models each with a distinct unresolved tag yield two W004s attributed to the right model

Test plan

  • cd engine && cargo test -p rocky-compiler — 249 unit + 15 integration tests pass, including the 5 new w004_* cases
  • cd engine && cargo test --workspace — all crate test suites green
  • cd engine && cargo clippy --workspace --all-targets -- -D warnings — clean
  • cd engine && cargo fmt --check — clean
  • just codegen leaves git status clean (W004 is a pure compiler-layer diagnostic, no CLI output-struct changes, no schemas cascade)

🤖 Generated with Claude Code

@hugocorreia90 hugocorreia90 force-pushed the feat/compiler-w004-unresolved-classification-tags branch 2 times, most recently from a1fa54c to f53c6c5 Compare April 24, 2026 16:43
…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).
@hugocorreia90 hugocorreia90 force-pushed the feat/compiler-w004-unresolved-classification-tags branch from f53c6c5 to ca4a32e Compare April 24, 2026 17:17
@hugocorreia90 hugocorreia90 merged commit 56913d2 into main Apr 24, 2026
13 checks passed
@hugocorreia90 hugocorreia90 deleted the feat/compiler-w004-unresolved-classification-tags branch April 24, 2026 17:22
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant