finowl guard
finowl guard [OPTIONS] -b, --bundle PATH Path to the databricks.yml bundle file. [default: databricks.yml] -t, --target TEXT Target environment to act on. [required] -o, --output [table|json] Output format. [default: table] -p, --profile TEXT Authentication profile to act as. [env: FINOWL_CONFIG_PROFILE]
Exit codes
| CODE | MEANING | IN CI |
|---|---|---|
| 0 | Every rule in force passed, or warned. | The step succeeds. |
| 1 | At least one rule with action: block was violated. | The step fails. This is the gate. |
| 2 | A check could not run — no credential, unknown target, missing workload words, pricing unavailable, malformed bundle. | The step fails, and the reason is a setup problem rather than a cost one. |
1 and 2 mean different things and deserve different responses. 1 is the product working: something costs too much. 2 means nothing was checked, so treating it as a pass leaves the pipeline ungated.What it does, in order
- Checks the bundle exists. Exits
2naming the path it looked for. - Checks the target against the bundle's
targets:block. Before the platform is contacted at all — naming an environment that does not exist is answerable from the bundle alone. - Resolves the credential.
- Verifies the project identity and fails on name drift between
finowl.ymland the platform. - Fetches the rules— account and organization policies merged, filtered to this environment, with a budget rule carrying this product's own budget.
- Reads the bundle resolved for the target, and applies the workload gate.
- Evaluates cost against the diff, and governance against the resolved bundle.
- Reports the run, then exits.
The cost check
Cost is evaluated against the change, using the same diff finowl diff produces. The guard evaluates the P90 total, not the midpoint.
$ finowl guard --target prod
Product-wide total
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
┃ Component ┃ Note ┃ Amount ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
│ This target (after change) │ estimation │ 193.49 │
│ Other environments │ recorded estimations │ 130.61 │
│ Storage & data transfer │ not yet measured │ — │
└────────────────────────────┴───────────────────────┴────────┘
Blocked Thresholds
┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃ Threshold ┃ Type ┃ Limit ┃ Actual ┃ Action ┃
┡━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
│ Max cost increase ($) │ increase_absolute │ 50.0 │ 58.40 │ BLOCK │
└───────────────────────┴───────────────────┴───────┴────────┴────────┘
Passed Thresholds
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃ Threshold ┃ Type ┃ Limit ┃ Actual ┃ Action ┃
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
│ Budget exceeding │ absolute │ 400.0 │ 324.10 │ BLOCK │
└──────────────────┴──────────┴───────┴────────┴────────┘
The absolute limit is this product's own budget, not an org default.
✗ BLOCKED — one or more block thresholds exceeded.When the cost check is skipped
| SITUATION | MESSAGE | EFFECT |
|---|---|---|
| No cost rules reach this product | Warning: No cost policy defined — cost check skipped. | Governance still runs, and the bundle plan is never run — so this is the one case where guard needs no Databricks workspace access. |
| No cluster changes in the plan | No cluster changes detected — costs are unchanged. | Passes. There is nothing to price a movement on. |
| The product has no committed budget | The budget rule drops out of the verdict. | The total is still reported. A rule cannot invent a ceiling nobody agreed to. |
The governance check
Governance rules are evaluated against the target-resolved bundle, not the base declaration. A target overlay can replace job_clusters wholesale, so the guard has to check what is actually being deployed.
Governance Policy Results
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┓
┃ Rule ┃ Type ┃ Action ┃ Status ┃ Resources ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━┩
│ Team tag on compute │ required_tag │ BLOCK │ FAIL │ 1 │
│ Resource naming │ naming_convention │ WARN │ WARN │ 1 │
│ Compute policy required │ required_compute_policy │ BLOCK │ PASS │ — │
└─────────────────────────┴─────────────────────────┴────────┴────────┴───────────┘
✗ BLOCKED — one or more governance rules failed.One result per scoped surface. A rule with scope: [job, warehouse] produces two results, and each names the resources that failed it. Full reference in Governance rules.
In CI
- name: Cost and governance guard
run: finowl guard --target "$BUNDLE_TARGET"That is the whole integration. No API key, no threshold in the workflow file, nothing to redeploy when a rule changes — the rules are fetched at run time, so an administrator tightening a policy takes effect on the next pipeline run.
Put the guard before the deploy step and make the deploy depend on it. A gate that runs after the deployment reports a cost that already exists.
Warn first
Introducing a rule as warn and watching what it would have caught costs nothing and tells you whether the threshold is right. A warn rule can never fail a build. Promote it to block once the noise is gone.
JSON output
finowl guard -t prod -o json{
"command": "guard",
"target": "prod",
"passed": false,
"result": {
"cost": {
"passed": false,
"delta": 58.40,
"cost_before": 265.70,
"cost_after": 324.10,
"violations": [ { "name": "Max cost increase ($)", "type": "increase_absolute",
"limit": 50.0, "actual": 58.40, "action": "block" } ],
"warnings": [],
"checked": [ ... ],
"resources": [ ... ]
},
"governance": {
"passed": true,
"results": [ ... ]
}
}
}passedappears on the envelope and again on each ofcostandgovernance, so a consumer never has to re-derive which half failed.--output jsondoes not change the exit code. Parse the output and check$?.