Fluid Forge
Why Forge
Concepts
Get Started
  • Consume a Data Product
  • See it run
  • Demos
  • Local (DuckDB)
  • Source-Aligned (Postgres → DuckDB)
  • AI Forge + Data Models
  • MCP Output Port — Serve to AI Agents
  • GCP (BigQuery)
  • Snowflake Team Collaboration
  • Declarative Airflow
  • Orchestration Export
  • Jenkins CI/CD
  • Universal Pipeline
  • 11-Stage Production Pipeline
  • Catalog Forge End-to-End
CLI Reference
  • Agent Policy (concept)
  • MCP Output Port — Serve to Agents
  • MCP deep-dive
  • AI-assisted authoring
  • LLM providers & backends
  • Overview
  • Quickstart
  • Examples
  • Your own CI
  • Your own scaffolding
  • Custom validator
  • Apply hook
  • Reference
  • Overview
  • Architecture
  • GCP (BigQuery)
  • AWS (S3 + Athena)
  • Snowflake
  • Local (DuckDB)
  • Custom Providers
  • Roadmap
GitHub
Why Forge
Concepts
Get Started
  • Consume a Data Product
  • See it run
  • Demos
  • Local (DuckDB)
  • Source-Aligned (Postgres → DuckDB)
  • AI Forge + Data Models
  • MCP Output Port — Serve to AI Agents
  • GCP (BigQuery)
  • Snowflake Team Collaboration
  • Declarative Airflow
  • Orchestration Export
  • Jenkins CI/CD
  • Universal Pipeline
  • 11-Stage Production Pipeline
  • Catalog Forge End-to-End
CLI Reference
  • Agent Policy (concept)
  • MCP Output Port — Serve to Agents
  • MCP deep-dive
  • AI-assisted authoring
  • LLM providers & backends
  • Overview
  • Quickstart
  • Examples
  • Your own CI
  • Your own scaffolding
  • Custom validator
  • Apply hook
  • Reference
  • Overview
  • Architecture
  • GCP (BigQuery)
  • AWS (S3 + Athena)
  • Snowflake
  • Local (DuckDB)
  • Custom Providers
  • Roadmap
GitHub
  • Introduction

    • Home
    • Why Fluid Forge
    • Getting Started
    • Snowflake Quickstart
    • See it run
    • Forge Data Model
    • Vision & Roadmap
    • Playground
    • FAQ
  • Concepts

    • Concepts
    • Builds, Exposes, Bindings
    • What is a contract?
    • Quality, SLAs & Lineage
    • Governance & Policy
    • Agent Policy (LLM/AI governance)
    • Providers vs Platforms
    • Fluid Forge vs alternatives
  • Data Products

    • Consume a Data Product
    • Product Types — SDP, ADP, CDP
  • Walkthroughs

    • Walkthrough: Local Development
    • Source-Aligned: Postgres → DuckDB → Parquet
    • AI Forge And Data-Model Journeys
    • Walkthrough: MCP Output Port
    • Walkthrough: Deploy to Google Cloud Platform
    • Walkthrough: Snowflake Team Collaboration
    • Declarative Airflow DAG Generation - The FLUID Way
    • Generating Orchestration Code from Contracts
    • Jenkins CI/CD for FLUID Data Products
    • Universal Pipeline
    • The 11-Stage Pipeline
    • End-to-End Walkthrough: Catalog → Contract → Transformation
  • CLI Reference

    • CLI Reference
    • Core workflow

      • fluid init
      • fluid demo
      • fluid forge
      • fluid validate
      • fluid plan
      • fluid apply
      • fluid diff
      • fluid status
    • Build & ship

      • fluid bundle
      • fluid generate
      • fluid generate artifacts
      • fluid validate-artifacts
      • fluid verify-signature
      • fluid generate iac
      • fluid generate-airflow
      • fluid generate-pipeline
      • fluid viz-graph
      • fluid publish
      • fluid ship
      • fluid rollback
      • fluid schedule-sync
    • AI & Agents

      • fluid ai
      • fluid agents
      • fluid mcp
      • fluid memory
      • fluid stats
      • fluid skills
    • Quality & governance

      • fluid test
      • fluid verify
      • fluid contract-tests
      • fluid contract-validation
      • fluid policy
      • fluid policy check
      • fluid policy compile
      • fluid policy apply
    • Standards & interoperability

      • fluid odps
      • fluid odps-bitol
      • fluid odcs
      • fluid export
      • fluid export-odps
      • fluid exporters
      • fluid import
      • fluid market
      • fluid datamesh-manager
    • Project & workspace

      • fluid product-new
      • fluid product-add
      • fluid workspace
      • fluid contract
      • fluid split
      • fluid config
      • fluid providers
      • fluid plugins
      • fluid provider-init
      • fluid auth
      • fluid secrets
      • fluid ide
      • fluid scaffold-ci
      • fluid scaffold-composer
      • fluid scaffold-ide
      • fluid docs
      • fluid runs
      • fluid retention
      • fluid describe
      • fluid doctor
      • fluid roadmap
      • fluid version
    • Catalog adapters

      • Source Catalog Integration (V1.5)
      • Publishing to a Catalog — Overview
      • BigQuery Catalog
      • Snowflake Horizon Catalog
      • Databricks Unity Catalog
      • Google Dataplex Catalog
      • AWS Glue Data Catalog
      • DataHub Catalog
      • Data Mesh Manager Catalog
      • OpenMetadata Catalog
    • CLI by task

      • CLI by task
      • Add quality rules
      • Add agent governance
      • Debug a failed pipeline run
      • Switch clouds with one line
  • Recipes

    • Recipes
    • Recipe — add a quality rule
    • Recipe — switch clouds with one line
    • Recipe — tag PII in your schema
    • Write a contract that consumes another contract
    • Generate per-environment overlays
  • SDK & Plugins

    • SDK & Plugins
    • Quickstart — your first plugin
    • Examples

      • Runnable examples
      • Example: hello-scaffold — the minimal viable plugin
      • Example: gitlab-ci-scaffold — generate a complete CI project
      • Example: steward-validator — a custom governance rule
      • Example: prod-key-guard — apply-time invariant check
    • Journeys

      • Journeys
      • Your own CI/CD

        • You have your own CI/CD setup, no problem
        • GitLab CI — the bundle template
        • GitHub Actions — the bundle template
        • Jenkins — the bundle template
        • CircleCI — the bundle template
      • You have a strict project layout, no problem
      • You have governance rules, no problem
      • You want a check at apply time, no problem
    • Reference

      • Reference
      • Roles reference
      • Entry points reference
      • Trust model
      • Packaging
      • Companion packages
  • Providers

    • Providers
    • Provider Architecture
    • GCP Provider
    • AWS Provider
    • Snowflake Provider
    • Local Provider
    • Creating Custom Providers
    • Provider Roadmap
  • AI & Agents

    • MCP Server
    • Built-in And Custom Forge Guidance
    • Forge Discovery Guide
    • Forge Memory Guide
    • Authoring Forge Tools
    • Guided fluid forge UX
    • LLM Providers
    • LiteLLM Backend
    • Capability Warnings
    • Cost Tracking
    • FLUID Forge Contract GPT Packet
    • Agentic Primitives
  • Operate & Deploy

    • Airflow Integration
    • Blueprints
    • Source-Aligned Acquisition
  • Govern & Secure

    • Governance, Compliance & the Business Case
    • Governance & Compliance
    • Network Safety
    • Credential Resolver — Security Model
  • Configuration & Reference

    • Environment Variables
    • Typed Errors
    • Typed CLI Errors
    • API Stability — fluid_build.api
  • Architecture & Releases

    • V1.5 Catalog Integration — Architecture Deep-Dive
    • V1.5 + V2 Hardening — Release Notes
  • Project

    • Contributing to Fluid Forge
    • Fluid Forge Docs Baseline: CLI 0.9.0
    • Fluid Forge Docs Baseline: CLI 0.8.11
    • Fluid Forge Docs Baseline: CLI 0.8.10
    • Fluid Forge Docs Baseline: CLI 0.8.9
    • Fluid Forge Docs Baseline: CLI 0.8.8
    • Fluid Forge Docs Baseline: CLI 0.8.7
    • Fluid Forge Docs Baseline: CLI 0.8.6
    • Fluid Forge Docs Baseline: CLI 0.8.5
    • Fluid Forge Docs Baseline: CLI 0.8.4
    • Fluid Forge Docs Baseline: CLI 0.8.3
    • Fluid Forge Docs Baseline: CLI 0.8.0
    • Fluid Forge Docs Baseline: CLI 0.7.11
    • Fluid Forge Docs Baseline: CLI 0.7.9
    • Fluid Forge v0.7.1 - Multi-Provider Export Release

Example: steward-validator — a custom governance rule

A Validator plugin that fails any contract missing a data-steward identifier. Demonstrates how to encode governance/compliance rules that run automatically at fluid validate.

Source: Agenticstiger/forge-cli-sdk → examples/steward-validator/.

What it does

Every fluid contract must declare metadata.labels["principal.steward.id"]. Optionally, metadata.labels["principal.steward.email"] for ops notifications. The validator emits an error if the id is missing, a warning if the email is missing.

fluid validate contract.fluid.yaml
# ✗ extensions.steward-required: error STEWARD_ID_MISSING:
#   Contract 'order-events' is missing the required label 'principal.steward.id'.
#   → Add metadata.labels['principal.steward.id'] with the employee identifier of the data steward.

When the contract is fixed:

fluid validate contract.fluid.yaml
# ✓ Contract valid against fluidVersion 0.7.3

Once installed (pip install steward-validator), the rule runs on every fluid validate invocation — your governance becomes part of the CI gate without each team having to configure anything.

Layout

steward-validator/
├── pyproject.toml
├── src/steward_validator/
│   ├── __init__.py
│   └── validator.py               ← ~90 lines, full source below
├── tests/
│   └── test_validator.py          ← 97 lines, scenarios for the rule
└── demo.py

pyproject.toml

[project]
name = "steward-validator"
version = "0.1.0"
description = "FLUID Validator example — fails contracts that don't declare a data steward"
requires-python = ">=3.10"
dependencies = ["data-product-forge-sdk>=0.10,<1"]

# Note the entry-point GROUP — different from CustomScaffold's:
[project.entry-points."fluid_build.validators"]
steward-required = "steward_validator.validator:StewardValidator"

The group is fluid_build.validators (for Validator plugins discovered at instantiation time). The CLI also has a fluid_build.extension_validators group for plugins that validate a sub-key of contract.extensions — different mechanism, covered in the entry-points reference.

src/steward_validator/validator.py

"""Steward Validator — fails any contract missing a data-steward identifier."""

from __future__ import annotations

from typing import Any, List, Mapping

from fluid_sdk import (
    ContractHelper,
    Finding,
    PluginMetadata,
    Validator,
)


class StewardValidator(Validator):
    """Fails the contract validation if a steward identifier is missing."""

    name = "steward-required"

    @classmethod
    def get_plugin_info(cls) -> PluginMetadata:
        return PluginMetadata(
            name=cls.name,
            role=cls.role,
            display_name="Steward Required Validator",
            description=(
                "Enforces that every contract declares "
                "metadata.labels['principal.steward.id']."
            ),
            version="0.1.0",
            author="FLUID SDK Examples",
            tags=["governance", "compliance"],
        )

    def plan(self, contract: Mapping[str, Any]) -> List[dict]:
        c = ContractHelper(contract)
        findings: List[Finding] = []

        labels = c.metadata.get("labels") or {}
        steward_id = labels.get("principal.steward.id")
        steward_email = labels.get("principal.steward.email")

        if not steward_id:
            findings.append(
                Finding(
                    severity="error",
                    code="STEWARD_ID_MISSING",
                    message=(
                        f"Contract {c.id!r} is missing the required label "
                        f"'principal.steward.id'."
                    ),
                    path='metadata.labels["principal.steward.id"]',
                    remediation=(
                        "Add metadata.labels['principal.steward.id'] with the "
                        "employee/user identifier of the data steward."
                    ),
                )
            )

        if steward_id and not steward_email:
            findings.append(
                Finding(
                    severity="warn",
                    code="STEWARD_EMAIL_MISSING",
                    message=(
                        f"Contract {c.id!r} declares a steward id but no email — "
                        "operations notifications will go nowhere."
                    ),
                    path='metadata.labels["principal.steward.email"]',
                    remediation=(
                        "Add metadata.labels['principal.steward.email'] with the "
                        "team / steward email."
                    ),
                )
            )

        return [f.to_action() for f in findings]

Finding is the SDK's structured-finding type. Severity is one of info / warn / error / critical — these map to the Severity str-enum added in SDK 0.10.0, whose Severity.coerce fails safe (an unrecognised severity counts as error). The CLI's exit code is derived from the maximum severity emitted across all validator plugins.

Validator.plan(contract) returns a list of PluginAction dicts (each Finding.to_action() produces one). The Validator base class's default apply() summarizes findings by severity and writes them to the validation report.

Tests

# tests/test_validator.py (excerpts)
# ValidatorTestHarness (SDK 0.10.0) runs the 13 generic invariants plus
# validator-specific conformance. Subclass it directly for any Validator.

class TestStewardValidator(ValidatorTestHarness):
    plugin_class = StewardValidator
    sample_contracts = [LOCAL_CONTRACT, STRICT_GOVERNANCE_CONTRACT]

    def test_missing_steward_id_is_error(self):
        plugin = self._instantiate()
        actions = plugin.plan(CONTRACT_WITHOUT_STEWARD)
        findings = [a for a in actions if a["op"] == "emit_finding"]
        assert any(f["params"]["severity"] == "error" and
                   f["params"]["code"] == "STEWARD_ID_MISSING"
                   for f in findings)

    def test_missing_steward_email_is_warning(self):
        plugin = self._instantiate()
        actions = plugin.plan(CONTRACT_WITH_ID_NO_EMAIL)
        findings = [a for a in actions if a["op"] == "emit_finding"]
        assert any(f["params"]["severity"] == "warn" and
                   f["params"]["code"] == "STEWARD_EMAIL_MISSING"
                   for f in findings)

    def test_fully_specified_contract_passes_clean(self):
        plugin = self._instantiate()
        actions = plugin.plan(CONTRACT_WITH_STEWARD_AND_EMAIL)
        findings = [a for a in actions if a["op"] == "emit_finding"]
        assert findings == []

Run it

# In the steward-validator/ directory:
pip install -e ".[dev]"
pytest
# ============== 16 passed in 0.08s ===============

End-to-end against a real contract:

pip install data-product-forge steward-validator
fluid validate contract.fluid.yaml
# (auto-runs your validator alongside core schema validation)

If the contract lacks the steward id:

✗ Validation failed
  Errors:
    - extensions.steward-required: STEWARD_ID_MISSING:
      Contract 'order-events' is missing the required label 'principal.steward.id'.

  Warnings:
    (none)

If only the email is missing:

⚠ Validation passed with warnings
  Warnings:
    - extensions.steward-required: STEWARD_EMAIL_MISSING:
      Contract 'order-events' declares a steward id but no email — operations notifications will go nowhere.

You'll know it worked when

  • All tests pass under pytest.
  • the importlib.metadata.entry_points one-liner above shows steward-required under validators.
  • Running fluid validate against a contract without the steward id label exits non-zero with the structured error.
  • Running against a contract with id but no email exits 0 with a warning.
  • Running against a fully-specified contract exits 0, no findings.

When not to use a Validator

If the check needs to run at apply time (not at author/validate time) — e.g., verifying a bundle digest hasn't drifted, or that an external secret has been resolved — use an apply hook instead. See apply-hook-prod-key-guard.

If the rule is per-extension-block (e.g., validating the shape of contract.extensions.customScaffold), use an extension validator via the fluid_build.extension_validators entry-point group — different mechanism, lighter weight. The entry-points reference compares the two.

Next

  • Apply-hook example — same shape but runs at fluid apply
  • Journeys → custom-validator — full walkthrough of governance plugin authoring
  • Reference → roles — what Validator inherits
Edit this page on GitHub
Last Updated: 6/27/26, 4:58 PM
Contributors: fas89
Prev
Example: gitlab-ci-scaffold — generate a complete CI project
Next
Example: prod-key-guard — apply-time invariant check