Skip to content

ADR-0015: Policy Enforcement as Core Module

Accepted

Platform teams need compile-time enforcement of:

  • Naming conventions (medallion pattern: bronze_, silver_, gold_*)
  • Test coverage thresholds (minimum 80%)
  • Documentation requirements (descriptions on models, columns)
  • SQL quality (linting, structure validation)

Per ADR-0037 (Composability Principle), plugin interfaces are appropriate when:

  • Multiple implementations exist OR may exist
  • Organizations may swap implementations

Policy enforcement fails these criteria:

  1. Tooling is already pluggable via DBTPlugin (ADR-0043):

    • lint_project() → SQLFluff (dialect-aware SQL linting)
    • validate_artifacts() → dbt-checkpoint (manifest validation)
  2. Organizational rules are configuration, not implementations:

    • Naming conventions differ by organization, not by tool
    • Test coverage is a threshold (80%), not an algorithm
    • Documentation requirements are enumerated fields
  3. No real-world alternatives:

    • dbt-checkpoint is the only mature dbt artifact validator
    • SQLFluff is the standard SQL linter
    • Both are needed together (they validate different things)
ConcernTimingOwnerTool
PolicyEnforcerCompile-timeCore moduledbt-checkpoint, SQLFluff (via DBTPlugin)
DataQualityPluginRuntimePlugin (ADR-0044)Great Expectations, Soda

Policy enforcement is a CORE MODULE in floe-core, not a pluggable interface.

┌─────────────────────────────────────────────────────────────────┐
│ Layer 2: CONFIGURATION (manifest.yaml) │
│ │
│ governance: │
│ naming: │
│ enforcement: strict # off | warn | strict │
│ pattern: medallion # medallion | kimball | custom│
│ quality_gates: │
│ minimum_test_coverage: 80 │
│ require_descriptions: true │
│ block_on_failure: true │
└──────────────────────────┬──────────────────────────────────────┘
│ Consumed by
┌─────────────────────────────────────────────────────────────────┐
│ Layer 1: FOUNDATION (floe-core) │
│ │
│ PolicyEnforcer (core module, NOT a plugin) │
│ ├── apply_naming_rules(manifest, config) │
│ ├── apply_coverage_rules(manifest, config) │
│ └── apply_documentation_rules(manifest, config) │
│ │
│ DBTPlugin (pluggable via ADR-0043) │
│ ├── lint_project() → SQLFluff │
│ └── validate_artifacts() → dbt-checkpoint │
└─────────────────────────────────────────────────────────────────┘
floe_core/enforcement/policy_enforcer.py
from floe_core.schemas import GovernanceConfig
class PolicyEnforcer:
"""Core module for compile-time policy enforcement.
NOT a plugin - organizational rules are configuration,
not swappable implementations. Located in floe_core/enforcement/,
NOT in plugin_interfaces.py.
"""
def enforce(
self,
manifest: dict[str, Any],
config: GovernanceConfig,
) -> EnforcementResult:
"""Apply all governance rules to dbt manifest.
Args:
manifest: Parsed dbt manifest.json
config: Governance config from manifest.yaml
Returns:
EnforcementResult with violations and severity
"""
results = []
results.extend(self._apply_naming_rules(manifest, config.naming))
results.extend(self._apply_coverage_rules(manifest, config.quality_gates))
results.extend(self._apply_documentation_rules(manifest, config.quality_gates))
return EnforcementResult(violations=results)
def _apply_naming_rules(
self,
manifest: dict[str, Any],
naming_config: NamingConfig,
) -> list[Violation]:
"""Validate model names match configured pattern."""
...
def _apply_coverage_rules(
self,
manifest: dict[str, Any],
gates_config: QualityGatesConfig,
) -> list[Violation]:
"""Validate test coverage meets threshold."""
...
def _apply_documentation_rules(
self,
manifest: dict[str, Any],
gates_config: QualityGatesConfig,
) -> list[Violation]:
"""Validate models have required documentation."""
...
manifest.yaml
governance:
naming:
enforcement: strict # off | warn | strict
pattern: medallion # medallion | kimball | custom
custom_patterns: # Only if pattern: custom
- "^bronze_.*$"
- "^silver_.*$"
- "^gold_.*$"
quality_gates:
minimum_test_coverage: 80
require_descriptions: true
require_column_descriptions: false
block_on_failure: true
Terminal window
$ floe compile # planned root data-team command; not alpha-supported yet
[1/5] Loading platform manifest...
[2/5] Compiling dbt project...
[3/5] Linting SQL (via DBTPlugin)... # SQLFluff
[4/5] Validating artifacts (via DBTPlugin)... # dbt-checkpoint
[5/5] Enforcing governance rules... # EnforcementEngine (core)
Governance violations:
ERROR: 'stg_payments' violates medallion naming
Expected: bronze_*, silver_*, or gold_*
ERROR: 'customers' missing model description
Required by: quality_gates.require_descriptions
Compilation FAILED (block_on_failure: true)
  • Simpler architecture: No plugin interface to maintain for rules that don’t vary by implementation
  • Clear ownership: Tooling (SQLFluff, dbt-checkpoint) → DBTPlugin; Rules → Core
  • Reduced complexity: Policy enforcement stays in core instead of adding another plugin surface
  • Configuration-driven: Platform teams edit YAML, not code
  • Less extensible: Custom enforcement logic requires core changes
  • Mitigation: Custom rules can extend EnforcementEngine or contribute upstream
  • DBTPlugin still handles tooling: SQLFluff and dbt-checkpoint remain pluggable via ADR-0043
  • DataQualityPlugin unaffected: Runtime data validation remains a plugin (ADR-0044)