A Practical Policy Framework for Data Products in Microsoft Fabric

When organizations adopt “data as a product,” the hardest scaling problem is rarely technology—it’s consistency. The moment multiple domains publish datasets, semantic models, pipelines, and “gold outputs,” you need guardrails that are (1) explicit, (2) automatable, and (3) enforceable at the right points in the lifecycle. That is exactly what a Data Product Policy Framework should provide: a lightweight but opinionated set of rules that protect interoperability without killing autonomy.

Below is a pragmatic policy set (including your examples) plus a first architecture proposal for a Policy Service built as part of a central Platform Management domain.

Policy categories (with concrete examples)

Naming and discoverability policies

Naming conventions sound basic—until you try to automate lineage, CI/CD comparisons, and cross-domain discovery.

Examples

  • Purpose-driven naming
    dp_<domain>_<product>_<layer> (internal assets) vs. op_<domain>_<product>_<contract> (output ports).
  • Environment encoding
    Enforce suffix/prefix conventions for DEV/TEST/PROD artifacts, and block “PROD-looking” names in non-prod.
  • Folder/name consistency
    In Fabric, folders can effectively become part of an item’s identity for comparisons; moving items between folders is treated as “different” even without a schema change. Microsoft Learn
    Policy implication: standardize folder patterns (or ban ad-hoc foldering for governed assets).
  • Character set constraints
    If you manage semantic models as code using TMDL, object names with special characters require quoting rules; you can make this a policy to keep names automation-friendly. Tabellarische Modelldefinitions…

Typical enforcement levels

  • Warn on minor deviations (case, separators)
  • Block on collisions, environment misuse, forbidden characters, or missing purpose tags

Consumption policies by medallion layer

Medallion is a great mental model—policies make it real.

Examples

  • Layer direction rule (default)
    Allow: Bronze → Silver → Gold.
    Block: Gold → Silver (anti-pattern) or “downward reads” unless explicitly exempted.
  • Cross-domain consumption rule
    Cross-domain reads must go through an Output Port (contracted interface) and not directly into another domain’s internal lakehouse/warehouse.
  • Skip-layer rule
    Only allow Bronze → Gold if a product declares and tests the transformation contract (exception policy with expiry).

Dependency integrity policies (no self-consumption, no cycles)

This is one of the highest ROI policy classes because it prevents “silent architecture rot.”

Examples

  • No self-reference
    A Data Product must not directly or indirectly consume itself (same product ID across any dependency edge).
  • No cycles (global DAG rule)
    The overall dependency graph across products and output ports must be acyclic.
  • Blast-radius control
    Limit the maximum depth of dependency chains for certain asset types (e.g., “Gold outputs must not depend on more than N upstream products”).

Implementation note: these checks are easiest if every product declares dependencies explicitly in its metadata (not inferred only from lineage).

Contract and schema evolution policies

In product thinking, the contract is the product boundary.

Examples

  • Schema versioning
    Require semantic versioning (MAJOR.MINOR.PATCH) per output port contract.
  • Breaking change protection
    Block breaking changes unless MAJOR increments and a deprecation window is provided.
  • Schema hash in events
    When a product publishes “snapshot sealed” (or equivalent), include schemaHash and contractVersion in the event payload; downstream products validate before processing.

For semantic models: keeping definitions in TMDL (text-based, source-control-friendly) enables pull-request validation (naming, banned patterns, required descriptions). Tabellarische Modelldefinitions…

Access and entitlement policies (consumer conditions)

This is where “data sharing” becomes safe and auditable.

Examples

  • Subscription required
    A consumer must register a subscription (purpose, owner, retention, classification) before access is granted.
  • Data classification gating
    If a product is tagged “PII/Restricted,” only allow consumers with appropriate entitlement and approved use case.
  • Least privilege by layer
    Bronze often requires stricter access than Gold (counterintuitive, but realistic if Bronze contains raw sensitive attributes).

Quality, freshness, and operational readiness policies

If a product is not reliable, it is not a product.

Examples

  • Required quality gates per layer
    Bronze: completeness + ingestion window.
    Silver: validity + deduplication.
    Gold: business rule checks + KPI stability.
  • SLO declaration
    Require each output port to publish an SLO (e.g., “Event-to-Ready < 30 minutes, 99%”) and capture evidence.
  • Observability minimum
    Must emit run status + data quality summary into the management domain (for your heatmap/status dashboard approach).

FinOps and platform safety policies

Often forgotten until the first capacity surprise.

Examples

  • Budget guardrails
    Enforce cost ceilings per product tier (e.g., “Gold products must provide cost attribution tags; repeated overruns require review”).
  • Workload placement
    Restrict certain heavy workloads to approved capacities or time windows.

Architecture proposal: Policy Service inside Platform Management

A robust pattern is to separate policy decisions from policy enforcement. Open Policy Agent (OPA) is a proven way to implement “policy as code” behind a simple API, and it is explicitly designed to offload decision-making from services. openpolicyagent.org

Core building blocks

1) Policy Administration Point (PAP)

  • “Policy Portal” in your Platform Management UI (Angular + Tailwind + PrimeNG) to manage:
    • policy sets, versions, severities (warn/block)
    • exceptions with owners + expiry
    • mapping rules to domains/products/layers

2) Policy Decision Point (PDP)

  • A Policy Service (C#/.NET in Azure Web App) that exposes:
    • POST /evaluate (decision + reasons)
    • POST /simulate (for CI/CD and testing)
    • GET /policies (catalog, versions)

Internally, PDP can call OPA (sidecar or separate container) for policy evaluation. openpolicyagent.org

3) Policy Information Points (PIP)
Where the PDP gets “facts” from:

  • Fabric Metadata Collector (calls Fabric REST APIs to inventory items, lakehouses, semantic models, etc.). For example, Fabric provides REST endpoints to list items and lakehouses/semantic models within a workspace. Microsoft Learn+2Microsoft Learn+2
  • Dependency Graph Store (SQL via EF): stores declared dependencies + computed graph edges
  • Identity/Entitlements (Entra ID groups, domain roles, consumer registrations)
  • Contract Registry (output port schemas, versions, hashes; optionally TMDL artifacts)

4) Policy Enforcement Points (PEP)
Where decisions are applied:

  • CI/CD pipelines (Azure DevOps + PowerShell): validate pull requests and deployments before publish
  • Platform APIs (subscription creation, output port publishing, domain onboarding)
  • Event-driven gate (Service Bus subscription): validate “product published / contract changed” events before broadcasting them further

Service Bus fits naturally as the decoupling layer for these enforcement flows. Microsoft Learn+1

Recommended enforcement flows (minimum viable but effective)

Flow 1 — “Publish / Promote” gate (CI/CD + Deployment)

  1. Dev changes data product assets (notebooks, pipelines, schemas, TMDL models).
  2. Pipeline assembles a Change Manifest (what changed + intended target env).
  3. PEP (pipeline) calls PDP /evaluate with:
    • actor, environment, domain, layer
    • artifactNames, contractVersion, dependencies
  4. PDP pulls current facts from PIPs (Fabric inventory, graph store).
  5. Decision returned:
    • Allow (optionally with warnings)
    • Deny (block publish)

This is where naming, schema evolution, and “folder-as-name” issues should be caught early. Microsoft Learn

Flow 2 — “Consumer subscription” gate (runtime governance)

  1. Consumer requests access to an output port (purpose + classification + retention).
  2. Platform Management API calls PDP:
    • validate medallion rules, cross-domain constraints, classification/entitlement
  3. If allowed, provisioning proceeds (groups/roles/workspace permissions).

Flow 3 — “No cycles” gate (graph validation)

  1. Any time dependencies change (new subscription, new upstream link), emit an event.
  2. Policy Service updates dependency graph and runs cycle detection.
  3. If cycle detected:
    • block the change, or quarantine the subscription until resolved
    • emit a governance incident event to Service Bus

A few additional “high leverage” policies (often overlooked)

  • Mandatory ownership metadata: product owner, technical owner, domain, support channel; block if missing.
  • Data residency/compliance tags: enforce allowed regions/boundaries where required.
  • Deprecation policy: every breaking change requires a deprecation plan and end date.
  • Testing baseline: “Gold outputs must have data tests + SLA/SLO defined.”
  • Contract-first sharing: only output ports can be granted cross-domain reader access; internal lakehouse tables are non-shareable by policy.

Here is a concrete, end-to-end example (subscription + enforcement) and a sample YAML policy file.

Concrete example: “Subscription request” with medallion, entitlement, and cycle checks

Scenario

  • Producer data product (Domain: Customer):dp_customer_customer360
    • Output Port: op_customer_customer360_profile_v1
    • Medallion: Gold
    • Classification: PII (restricted)
  • Consumer data product (Domain: Claims):dp.claims.fraud_signals
    • Medallion: Gold
    • Wants to subscribe to op_customer_customer360_profile_v1 for “fraud detection”.

Step 1 — Consumer requests subscription (Platform Management API)

The consumer initiates a subscription request in the platform UI. Platform Management creates a Policy Evaluation Request (what the PDP evaluates):

{
  "action": "createSubscription",
  "environment": "prod",
  "consumer": {
    "productId": "dp_claims_fraud_signals",
    "domain": "claims",
    "layer": "Gold"
  },
  "provider": {
    "productId": "dp_customer_customer360",
    "domain": "customer",
    "layer": "Gold",
    "outputPort": {
      "id": "op.customer_customer360_profile_v1",
      "contractVersion": "1.0.0",
      "classification": "PII"
    }
  },
  "requestedPurpose": "fraud_detection",
  "requestor": {
    "userId": "jane.doe@company.com",
    "groups": ["claims-data-consumers", "pii-approved-fraud"]
  },
  "proposedDependencyEdge": {
    "from": "dp_claims_fraud_signals",
    "to": "dp_customer_customer360"
  }
}

Step 2 — Policy checks (PDP)

Your Policy Service (PDP) evaluates in this order (typical):

  1. Naming policy
    Confirms op_customer_customer360_profile_v1 matches the required convention.
  2. Medallion consumption policy
    Confirms a Gold consumer is allowed to consume Gold/Silver (depending on your rules) and that cross-domain reads go through an Output Port.
  3. Entitlement / classification policy
    Because the output is PII, the consumer must have an approved group and a declared purpose that is permitted.
  4. No self-consumption policy
    Blocks any request where consumer product == provider product.
  5. Cycle detection policy (global DAG)
    Checks whether adding the edge fraud_signals -> customer360 creates a cycle.

Step 3 — A cycle is detected (block)

Assume you already have an existing dependency somewhere else:

  • dp_customer_customer360 consumes dp_claims_fraud_signals (maybe as a feature signal enrichment)

That existing edge is:

  • dp_customer_customer360 -> dp_claims_fraud_signals

Now adding the new subscription edge:

  • dp_claims_fraud_signals -> dp_customer_customer360

creates a direct cycle.

Step 4 — Policy decision returned to Platform Management

{
  "decision": "deny",
  "reasons": [
    {
      "policyId": "deps.no_cycles",
      "severity": "deny",
      "message": "Dependency cycle detected: dp_claims_fraud_signals -> dp_customer_customer360 -> dp_claims_fraud_signals"
    }
  ]
}

Step 5 — User-facing outcome

  • Subscription creation is blocked.
  • Platform Management can propose remediation:
    • Break the cycle by introducing a stable, lower-level Output Port (e.g., customer publishes a “Silver profile facts” port, and claims publishes “fraud signals” as Gold—but neither depends on the other directly), or
    • Move one enrichment into a separate product that is explicitly “upstream-only”.

Sample YAML: policy-as-code for naming, medallion, self-reference, and cycles

This YAML assumes your Policy Service supports a small set of built-in check types (regex, equality, allow-rules, graph-cycle). You can implement these checks in C# and keep YAML purely declarative.

apiVersion: platform.management/v1
kind: PolicySet
metadata:
  name: data-product-core-policies
  version: 1.0.0
spec:
  defaults:
    environment: ["draft", "live"]

  rules:

    # 1) Naming policy: Output Port naming convention
    - id: naming.outputport
      severity: deny
      appliesTo:
        actions: ["publishOutputPort", "updateOutputPort"]
      check:
        type: regex
        field: provider.outputPort.id
        # Example: op_<domain>_<product>_<contract>_v<major>
        pattern: '^op\.[a-z0-9_]+\.[a-z0-9_]+\.[a-z0-9_]+\.v[0-9]+$'
      message: "Output Port id must match: op_<domain>_<product>_<contract>_v<major>"

    # 2) Medallion consumption rules (simple allow matrix)
    - id: consumption.medallion_matrix
      severity: deny
      appliesTo:
        actions: ["createSubscription"]
      check:
        type: allowMatrix
        consumerLayerField: consumer.layer
        providerLayerField: provider.layer
        allowed:
          Bronze: ["Bronze"]                 # keep Bronze local by default
          Silver: ["Bronze", "Silver"]
          Gold:   ["Silver", "Gold"]
      message: "Medallion rule violation: consumer layer may not consume provider layer."

    # 3) Cross-domain consumption must use Output Ports (no direct internal reads)
    - id: consumption.cross_domain_requires_output_port
      severity: deny
      appliesTo:
        actions: ["createSubscription"]
      when:
        type: notEquals
        leftField: consumer.domain
        rightField: provider.domain
      check:
        type: requiredField
        field: provider.outputPort.id
      message: "Cross-domain consumption must occur via an Output Port (provider.outputPort.id required)."

    # 4) Classification / entitlement gate (example for PII)
    - id: access.pii_requires_group_and_purpose
      severity: deny
      appliesTo:
        actions: ["createSubscription"]
      when:
        type: equals
        field: provider.outputPort.classification
        value: "PII"
      check:
        type: allOf
        checks:
          - type: contains
            field: requestor.groups
            value: "pii-approved-fraud"
          - type: in
            field: requestedPurpose
            values: ["fraud_detection", "claims_investigation"]
      message: "PII access requires approved group membership and an allowed purpose."

    # 5) No self-consumption (direct)
    - id: deps.no_self_reference
      severity: deny
      appliesTo:
        actions: ["createSubscription", "publishOutputPort"]
      check:
        type: notEquals
        leftField: consumer.productId
        rightField: provider.productId
      message: "A data product must not consume itself."

    # 6) No cycles (global dependency graph must remain a DAG)
    - id: deps.no_cycles
      severity: deny
      appliesTo:
        actions: ["createSubscription"]
      check:
        type: graphNoCycle
        graphSource: "ProductRegistry"   # your EF-backed store of nodes/edges
        proposedEdge:
          fromField: proposedDependencyEdge.from
          toField: proposedDependencyEdge.to
      message: "Subscription would introduce a dependency cycle in the data product graph."

  exceptions:
    # Optional: time-bound exception example
    - id: ex.skip_medallion_once
      policyIds: ["consumption.medallion_matrix"]
      scope:
        consumerProductIds: ["dp.analytics.experimental_product"]
      expiresOn: "2026-03-31"
      justification: "Temporary migration window; reviewed weekly by Platform Governance."

Leave a comment

About the author

I’m a data platform leader with 10+ years of experience in data modelling and Business Intelligence. Today, I lead the IT Data Platform at SWICA, working at the intersection of business needs and modern data engineering to turn complex data into reliable, valuable outcomes for the organization—and ultimately for our customers.

In my current role, I’m responsible for the operation and continuous evolution of a future-ready data platform. I focus on building scalable, cloud-based capabilities that enable teams to move faster while staying aligned with governance, security, and quality expectations. My strength lies in translating ambiguity into clear data products, robust pipelines, and BI solutions that people can trust.

Get updates

Spam-free subscription, we guarantee. This is just a friendly ping when new content is out.