Keep blueprint guardrails in place
A blueprint does more than scaffold a data product. It can also lock the parts that carry governance and compliance: infrastructure definitions, access controls, audit layout, and other files the platform team must keep intact.
Blueprint authors mark those files in the manifest as protected resources. After instantiation, the data product team owns the repository and can add pipelines, documentation, and other product-specific files. Protected paths must stay exactly as that blueprint version would generate them, with the parameter values recorded for the data product.
The guarantee is checked when a data product version is published. The platform rebuilds the expected files from the recorded blueprint version and compares them with the Git tag being released. If a protected file was edited, deleted, or replaced, a blocking policy rejects the release. The team sees the failure on the release as Policies Validation: Rejected, with the path that diverged.
| Moment | What happens |
|---|---|
| Instantiation | Protected files are written into the target repositories together with the rest of the blueprint output. Nothing is compared yet. |
| Everyday Git work | Commits and pull requests can still change any file, including a protected one. Git does not run a required check for this policy. |
| Blueprint update | An update opens a pull request that brings in the next blueprint version, including changes to protected files. The publication check does not run on that pull request. |
| Data product publication | The platform compares every protected path on the published tag with a fresh render of the recorded blueprint. This is the enforcement point. |
The check is off until the platform team enables it. Listing paths in the manifest has no effect on publication while the validator is inactive. See Turn the check on .
Field-by-field manifest reference, including targetRepositories and instantiation, lives in Develop a Blueprint
. This page covers how protection is declared, what people do when a release is blocked, and how the check behaves.
Mark resources as protected
Open the blueprint manifest and add a protectedResources list. Each entry names a path in the generated data product repository, after templates are rendered and files are placed in their destination folders. Paths inside the blueprint source tree, before that placement, are the wrong coordinate.
A single-repository blueprint only needs path. Omit repository to protect the target marked isRoot: true.
protectedResources:
- path: infrastructure/core/**
- path: docs/architecture.md
When the blueprint writes to more than one repository, set repository to a targetRepositories[].key for any path that does not land in the root repository. How that key is matched to the main or an additional repository at publication time is described in Multi-repository handling
.
protectedResources:
- path: application/**
- path: terraform/**
repository: infra-repo
Publish a new blueprint version so consumers receive the list. A catalog module cannot carry this list: publishing a module with a non-empty protectedResources fails with A Blueprint module must not declare protectedResources. Put the protection on the parent blueprint, using the folder where the module’s files land (for example data-plane/storage/**), not the path inside the module repository.
Leave the blueprint manifest and README out of the list under their source names. Instantiation moves them under .odm/blueprint/ on the root repository. Protect that destination path when those files must stay locked.
When a release is blocked
Publishing a data product version runs governance policies before the release is approved. While checks are in progress, Policies Validation on the Builder release card shows Pending. A blocking failure ends as Rejected (Policies validation failed). An approved release shows Approved.
Open the status (or the open-in-new icon on Policies Validation) to reach Policy Validation Results. The protected-resources policy appears under its display name, Blueprint Protected Resources Integrity unless an operator renamed it, from the engine Blueprint Service Validator. A blocking policy is marked Blocking and a failure is an Error. The alert quotes every mismatched path in one message, for example:
Protected resource 'infrastructure/core/**' is missing file 'infrastructure/core/main.tf' from the data product version— the file was removed or never committed on the tag.Protected resource 'docs/architecture.md': contents of file 'docs/architecture.md' differ from the blueprint— the file is present and its bytes differ.Protected resource 'infrastructure/core/**': file 'infrastructure/core/local.tf' is in the data product version but is not produced by the blueprint— an extra file appeared under a protected directory or glob.
The check reads the Git tag of the version you are publishing, not uncommitted work and not a later commit on the branch.
Restore the blueprint content
- Read the alert and note every path.
- Put those files back to the content the recorded blueprint version generates. Revert the commits that changed them, or copy the files from the blueprint checkpoint tag (
blueprint-v…) for that version. - Create a new Git tag that contains the restored files and publish that tag.
Editing a protected file so that it “looks acceptable” will fail again. The expected content is the blueprint render, including the parameter values stored for the data product.
Ask for a blueprint change
When the edit is legitimate (a new control, a renamed module, a path the team should own), the blueprint owner changes the template or the protectedResources list and publishes a new blueprint version. The data product team then runs a blueprint update
and publishes a data product version recorded against that blueprint version. The next check uses the new list and the new render.
A message that starts with Cannot check protected resources or Protected-resource check timed out means the platform could not complete the comparison (Git access, a missing tag, or a timeout). The release is still rejected when the policy is blocking. Restore nothing until the platform team confirms the check can run; a content fix will not clear an infrastructure failure.
If the policy was registered as non-blocking, the same mismatch is a Warning. Policies Validation can still show Approved. Treat the warning as a governance finding, not as permission to keep the edit, unless the platform team intended that mode.
Update a blueprint that protects files
Each published data product version is judged only by the blueprint version recorded on that release. A later blueprint version may add paths, drop paths, or point a path at a different repository. Older releases stay bound to the list they were published with.
To move a data product onto a new blueprint version:
- Publish the blueprint version that contains the new templates and the new
protectedResourceslist. - Start a blueprint update. Blindata opens a pull request per target repository. The update branch is a clean render of the new version; Git merges it with the team’s branch. See Update a Blueprint .
- If someone edited a protected file, that pull request often conflicts. Resolve the conflict by keeping the blueprint side for every protected path. Keep team-owned files that the blueprint does not generate.
- Merge the pull request, tag the result, and publish the data product version.
The update pull request is a normal Git merge. It does not approve or reject the release. After the merge, publication still rebuilds the expected tree and compares protected paths. An unchanged render (the same checkpoint reused) does not skip that comparison.
Files the team added outside protected paths stay in the repository across updates. A protected directory or glob is stricter: an extra file inside the match is a failure, because the published set of files must equal the rendered set.
How the check is configured
Manifest fields
protectedResources is optional. An empty list, or a data product version that was not created from a blueprint, makes this policy pass without reading Git. The result message is This blueprint does not declare protected resources or This data product version was not created from a blueprint.
| Field | Required | Meaning |
|---|---|---|
path |
Yes | File, directory, or glob relative to the destination repository root after instantiation. |
repository |
No | A targetRepositories[].key. Omitted or blank selects the single target with isRoot: true. |
Publishing a blueprint version rejects a repository value that is not a declared target key: Protected resource repository must match a targetRepositories[].key. The fix is to use a declared key, or omit repository to select the root target.
Hashes are computed when the release is checked. The manifest does not store a digest. The comparison uses the blueprint version and the parameter values recorded for the data product, so a hand-edited file fails even when the surrounding repository still looks plausible.
Full manifest rules are in Develop a Blueprint .
What a path matches
Paths use / separators and stay inside the destination repository.
| Pattern | Match |
|---|---|
docs/architecture.md |
That file. |
infrastructure/core |
Every regular file under that directory, recursively. |
infrastructure/core/** |
Glob. *, ?, […], and {…} are glob syntax and are matched from the repository root. |
application/** with no repository |
The same relative path on the root target. |
terraform/** with repository: infra-repo |
That glob on the repository mapped to infra-repo. |
Rules applied to every declaration:
- The path must match at least one regular file on the published tag and on the fresh render. A glob or directory that matches nothing on both sides fails.
- Comparison is the set of repository-relative file paths plus a SHA-256 digest of each file’s raw bytes. Renaming, deleting, adding, or changing bytes inside the match all fail.
.gitis skipped. Symbolic links are not followed; a link selected by the path fails the check.- An empty path, an absolute path, or a path that contains
..is invalid. - Only repositories named by the list are cloned. Other target repositories are ignored for this policy.
- The root tag is never reused as the tag of another repository. Each protected repository is read at its own recorded tag.
- Two logical targets that happen to point at the same remote are still compared separately.
Rendered files must be deterministic. A protected file that embeds a timestamp, a random id, or anything else that changes between two renders with the same parameters will fail even when nobody edited it.
Turn the check on
The validator is inactive by default. The platform team enables it on the Blueprint service and points that service at Policy Service. On startup the service registers a policy engine and a policy evaluated when a data product version is created. Git credentials for the check are service credentials configured here. They are separate from the token of the person who clicks publish.
blueprint:
validator:
active: true
evaluation-timeout-seconds: 120
policy-engine:
name: blueprint-service-validator
display-name: Blueprint Service Validator
policy:
name: Blueprint Protected Resources Integrity
blocking: true
git:
credentials:
- provider-type: GITHUB
auth-type: PAT
token: ${GIT_TOKEN}
odm:
product-plane:
policy-service:
active: true
address: http://policy-service:8005
| Property | Effect |
|---|---|
blueprint.validator.active |
true registers the policy and runs the check. false leaves protected paths unenforced. |
blueprint.validator.policy.blocking |
true (default) rejects the release on failure. false records a warning and allows approval. |
blueprint.validator.evaluation-timeout-seconds |
Default 120. The check fails if cloning and comparison exceed it: Protected-resource check timed out after 120s. |
blueprint.validator.git.credentials |
Provider credentials used to clone blueprint sources and published data product repositories. |
odm.product-plane.policy-service.active and address |
Policy Service must be reachable. If the validator is active but Policy Service is not configured, the policy is not registered. |
blocking is applied when the policy is first created. Changing blueprint.validator.policy.blocking later does not update the policy already stored in Policy Service. Change Blocking on that policy in Policy Service, or the release gate keeps the original mode.
The check fails closed when it applies and cannot finish: missing Git coordinates for a protected repository, unknown credentials, clone errors, a blueprint version that cannot be read, or a render error. Those failures stop immediately; remaining repositories are not cloned. Content mismatches are different: every mismatched path is collected and returned in one message.
Edge cases
- Composed modules. Only the parent blueprint’s list is enforced. Protect module output by its final destination path, on the repository that receives it. Module sidecars under
.odm/<module-alias>/follow that same destination. - Root-only files. The data product descriptor and
.odm/blueprint/exist on the root target. Arepositoryvalue that points those paths at another target fails, because the fresh render does not produce them there. - Subset of repositories. Protecting only the root is valid in a multi-repository layout. Repositories with no protected path are not required for this policy.
- Same relative path on two repositories. Declare the path twice, once per
repository. The two copies are independent. - Blueprint version drift. Publishing version 2 of a blueprint with a shorter list does not re-check products still recorded on version 1. The next data product release, after an update, uses version 2.
- Repository moves. The check uses the repository locations known at publication time. It is not a historical audit of an old URL after the data product repository has been moved.
- Parameter edits outside a blueprint update. Changing a value that the blueprint writes into a protected file, without going through a blueprint update that records the new parameters, shows up as a content mismatch.