R-Dash Wave 2 Pass 1 — M4 Semantic + M5 Governance Models + RLS Engine
Decision
Wave 2 pass 1 shipped in commit bb24b1f (2026-04-17). 13 files. M4 SemanticModel + SemanticMetric models with Cube.js YAML as source of truth + DB as metadata registry. M5 RLSPolicy + ColumnMask + Certification + SensitivityLabel + ContentSensitivity models with 3 enums (certification_status, column_mask_pattern, content_type) and 4 seeded sensitivity labels (public/internal/confidential/restricted). Alembic migration 20260417_1000_m4m5 creates 7 tables with full indices + constraints including CHECK that RLS policy scopes either data_source OR semantic_model (not both). Policy engine (apps/api/app/modules/governance/policy_engine.py) with UserContext frozen dataclass + _resolve_binding with 5 explicit sources (user_id, user_email, group_claim, role_any, constant) + render_predicate that converts :named params through the M3 ParameterizedQuery engine (SQL injection impossible by construction — no string concatenation of user claims into SQL anywhere) + apply_mask for 4 patterns (redact, hash, partial, nullify). 25 security-critical unit tests covering every binding source, injection-vector attempts (malicious emails, claim-embedded SQL injection), and all 4 mask patterns — 74 total unit tests now green. ADR-0009 documents Cube.js integration strategy (YAML+DB dual-sync, Tableau Pulse analog cited for reference). ADR-0010 documents RLS policy engine design (3-layer defense-in-depth, 5 binding sources, required paired contract test per J3, CI-blocking gate deferred to pass 2). Tableau MCP is available on VPS; cited as reference inspiration in ADRs only — NOT a runtime dependency per ADR-0002 Snowflake-only consumption rule.
Rationale
RLS is the single most security-critical mechanism in R-Dash. Misconfigured predicates have catastrophic blast radius. Building the policy engine FIRST (pass 1) with injection-resistance by construction + 25 unit tests before wiring any live policy means we have verified-safe foundations before touching real Snowflake data. The 5 explicit binding sources are exhaustive — any unknown source raises PolicyBindingError — eliminating the “accidentally add new source that bypasses safety” class of bugs. Reusing M3’s ParameterizedQuery engine means one injection-resistance code path for both user query params and RLS bindings. The CHECK constraint forcing RLSPolicy to scope either data_source OR semantic_model (XOR) prevents ambiguous policy application. The contract_test_path column is NOT NULL so no policy can be activated without a paired test — CI enforcement lands in pass 2 but schema enforcement is already here. Cube.js YAML-as-truth with DB-as-metadata pattern allows version-controlled metric definitions without coupling R-Dash’s schema to Cube’s internals. Tableau MCP availability is a reference-only signal per ADR-0002 — we cite Tableau Pulse Metric Definition as analog for SemanticMetric and Virtual Connection policy as analog for RLSPolicy, but never depend on Tableau at runtime.
Alternatives Rejected
Outcome
Pending
Related
- R-Dash Wave 1 CODE-COMPLETE
- r-same-m2-data-sources-m3-query-engine-foundation
- r-dash-architecture-freeze-checkpoint-2
- trpc-protected-procedure-insufficient-for-resource-authoriza
- widget-exactly-one-source-invariant-enforced-three-layers
- widget-exactly-one-source-triple-invariant
- widget-exactly-one-source-invariant-enforced-at-three-layers
- test-decision-from-hook-self-test-v2