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), includeschemaHashandcontractVersionin 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)
- Dev changes data product assets (notebooks, pipelines, schemas, TMDL models).
- Pipeline assembles a Change Manifest (what changed + intended target env).
- PEP (pipeline) calls PDP
/evaluatewith:actor,environment,domain,layerartifactNames,contractVersion,dependencies
- PDP pulls current facts from PIPs (Fabric inventory, graph store).
- 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)
- Consumer requests access to an output port (purpose + classification + retention).
- Platform Management API calls PDP:
- validate medallion rules, cross-domain constraints, classification/entitlement
- If allowed, provisioning proceeds (groups/roles/workspace permissions).
Flow 3 — “No cycles” gate (graph validation)
- Any time dependencies change (new subscription, new upstream link), emit an event.
- Policy Service updates dependency graph and runs cycle detection.
- 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)
- Output Port:
- Consumer data product (Domain: Claims):
dp.claims.fraud_signals- Medallion: Gold
- Wants to subscribe to
op_customer_customer360_profile_v1for “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):
- Naming policy
Confirmsop_customer_customer360_profile_v1matches the required convention. - 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. - Entitlement / classification policy
Because the output isPII, the consumer must have an approved group and a declared purpose that is permitted. - No self-consumption policy
Blocks any request where consumer product == provider product. - Cycle detection policy (global DAG)
Checks whether adding the edgefraud_signals -> customer360creates a cycle.
Step 3 — A cycle is detected (block)
Assume you already have an existing dependency somewhere else:
dp_customer_customer360consumesdp_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