> For the complete documentation index, see [llms.txt](https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/prevent/pr-guardrails/guardrail-types/slo-guardrails.md).

# SLO Guardrails

Gate a pull request when the repository already carries remediation that is overdue against your SLOs — the backstop that stops existing debt being ignored while new work keeps merging.

Every other guardrail type asks *what does this pull request introduce?* An SLO guardrail asks a different question: **does this repository still owe remediation that is past its deadline?**

It evaluates the **entire repository state**, not the diff. A pull request that adds nothing risky at all is still gated when the repository has dependency vulnerabilities overdue against your [Service Level Objectives](/mrecEO40m5D6bt7Pq5pE/operate/service-level-objectives-slos.md). That inverts the usual incentive — a team can no longer keep shipping around a backlog it isn't paying down, because the backlog is what's holding the merge.

SLO uses the `Repository` entity, so an SLO guardrail is **dedicated** — it can't be combined with dependency, code, or hygiene rules in the same policy.

{% hint style="warning" %}
**Requires SLOs configured first.** An SLO guardrail has nothing to enforce until you've set remediation timelines under [**Service Level Objectives**](/mrecEO40m5D6bt7Pq5pE/administer-and-monitor/program-policy/service-level-objectives.md). Configure those first, then build the guardrail.
{% endhint %}

## What it evaluates

|                       |                                                                                                |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| **Finding type**      | Open **dependency (SCA) vulnerabilities** only.                                                |
| **Evaluated against** | The whole repository — every code root, every open finding, regardless of what the PR touches. |
| **Frequency**         | Once per guardrail, per pull request evaluation.                                               |

{% hint style="warning" %}
**Dependency findings only.** An SLO guardrail reads open dependency vulnerabilities. Overdue **SAST**, **secrets**, **IaC**, and **agent file** findings do **not** trip it — even though SAST findings carry their own SLOs and appear on the SAST tab of the [SLO Overrides register](/mrecEO40m5D6bt7Pq5pE/operate/exceptions-and-overrides.md). To gate on new code-security findings, use a [SAST guardrail](/mrecEO40m5D6bt7Pq5pE/prevent/pr-guardrails/guardrail-types/sast-guardrails.md) alongside this one.
{% endhint %}

A finding counts toward the check only while it is still open. Heeler excludes anything already **remediated**, anything Heeler has marked fixed (the dependency was upgraded or removed, or the vulnerability no longer applies), and anything without a calculated due date.

## Creating one

In the guardrail builder, choose **SLO Violations** as the check type. SLO is a single-condition family — there are no **Type** or **Condition** dropdowns to work through, so the thresholds appear immediately. Scope and enforcement work the same as any other guardrail; see [Creating and Scoping a Guardrail](/mrecEO40m5D6bt7Pq5pE/prevent/pr-guardrails/building-guardrails/creating-and-scoping-a-guardrail.md).

## Thresholds

You set a **days-overdue threshold** per band. The threshold is grace on top of the deadline — the guardrail fires when:

```
now > slo_due_date + threshold_days
```

| Threshold          | Behavior                                                     |
| ------------------ | ------------------------------------------------------------ |
| `0`                | Fires the moment the SLO deadline passes — no grace.         |
| `N`                | Fires only once the finding is **N days past its deadline**. |
| *(band not added)* | That band is never enforced.                                 |

Each configured band is its own row: a severity or priority badge, a **Days Overdue Threshold** input, and a delete control. Bands you haven't configured sit behind the **Add Severity** (or **Add Priority**) button. Removing a row doesn't just zero it — it takes that band out of enforcement entirely.

{% hint style="info" %}
**The bands you see follow your SCA SLO strategy.** The **SCA** card on your [SLO settings](/mrecEO40m5D6bt7Pq5pE/administer-and-monitor/program-policy/service-level-objectives.md) runs on either **Heeler Risk** or **Severity**, and the builder shows only the matching set:

* **Heeler Risk** (the default) → **Urgent**, **Plan**, **Defer**
* **Severity** → **Critical**, **High**, **Medium**, **Low**

The other set is ignored at evaluation time, so it isn't offered. The **SAST** card's strategy has no bearing here — this guardrail only reads dependency findings.
{% endhint %}

{% hint style="warning" %}
**On the default settings, a new SLO guardrail starts with nothing enforced.** The builder pre-populates **Critical** at `0` days — a *Severity* band. But SCA SLOs default to the **Heeler Risk** strategy, where that band doesn't apply, so the builder opens with no rows and tells you plainly: *"No priorities configured — this rule will never trigger."* Click **Add Priority** and set at least one band before saving.

The same trap applies later: if you switch your SLO strategy after building the guardrail, its configured bands stop matching and the rule silently stops firing. Re-open the guardrail and configure the bands for the new strategy.
{% endhint %}

### Choosing thresholds

A threshold of `0` on every band is the strictest possible reading of your SLOs, and it is rarely where you want to start — on a mature repository it can block the first pull request you evaluate. Two patterns work better:

* **Start with a grace period.** Set the threshold to the grace you'd actually give a team — say `14` days past deadline on Urgent — so the guardrail catches genuine neglect rather than work that's already underway.
* **Start narrow, then widen.** Enforce your top band only (Urgent, or Critical) and add the lower bands once the top one is consistently clean.

Either way, run it in **Observe** first and watch the **Pull Requests** tab. Because this guardrail reads existing debt rather than the diff, its blast radius on day one is the size of your backlog — not the size of your change volume.

## How a PR clears the gate

There are three ways an SLO violation stops firing, and it's worth knowing all three because they have very different lead times.

{% stepper %}
{% step %}

### Fix it in the same pull request

If the pull request's own scan no longer finds the offending `package@version` in that code root, Heeler treats the finding as remediated by this PR and drops the violation. A developer who bumps the overdue dependency on their branch clears the block on the next check run — no separate PR, no waiting for the platform to re-scan the default branch.

This only applies to code roots the PR actually produced scan results for. A violation in a code root the PR didn't analyze stands.
{% endstep %}

{% step %}

### Fix it through Heeler

Trigger [Fix Now](/mrecEO40m5D6bt7Pq5pE/fix/sca-auto-fix.md) or the [remediation agent](/mrecEO40m5D6bt7Pq5pE/administer-and-monitor/program-policy/remediation-agent.md) on the overdue finding. Heeler opens its own validated fix PR; once that merges and the repository re-scans, the finding is remediated and the gate opens for everyone.

{% hint style="info" %}
**Guardrail Auto-Fix doesn't apply here.** The [Apply suggestion / Fix w/Heeler](/mrecEO40m5D6bt7Pq5pE/prevent/pr-guardrails/guardrail-auto-fix.md) flow on the pull request targets vulnerabilities *the PR introduces*, and it deliberately skips packages that were already present. Existing overdue debt is fixed through Fix Now or the remediation agent instead.
{% endhint %}
{% endstep %}

{% step %}

### Extend the deadline

Grant an [SLO override](/mrecEO40m5D6bt7Pq5pE/operate/exceptions-and-overrides.md) on the finding, with a reason (**No Fix Available**, **Fix Complexity**, **Not a Priority**, **Other**) and a new due date. The override replaces the calculated deadline and holds it there, so the guardrail reads the new date on its next run and the block lifts.

Heeler records the original deadline alongside the new one, so the **SLO Overrides** register shows exactly how far each exception moved the date and **who granted it** — which is what makes an extension an accountable decision rather than a quiet one.
{% endstep %}
{% endstepper %}

## What developers see on the PR

Because this guardrail reads the repository rather than the diff, the check tells the developer that the **repository** has remediation overdue — which findings breached their deadline, by how many days, and what the fix is.

Violations render under their own **SLO Violations** heading, grouped by guardrail and then by band, each band collapsible and labelled with its count and threshold — for example *🔴 Critical (3 findings) - Days overdue threshold: 14 days*. The rule is named **"Block PRs when open SLO violations exist in the repository"**.

For each overdue finding, the developer gets:

|                   |                                                                                                                          |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **Package**       | Name and version, badged **direct** or **transitive**, with an exploit badge when the vulnerability has a known exploit. |
| **Found in**      | The code root, linked into Heeler.                                                                                       |
| **Days overdue**  | Days past the *threshold*, not past the deadline — so `0` here means it has just crossed the line you set.               |
| **Solution**      | The recommended remediation with its **fixability** rating, which separates a version bump from a project.               |
| **Vulnerability** | ID, title, description and CVSS score, linked to the advisory.                                                           |

When a band carries **more than ten** findings, that band collapses into a compact table — CVE, package, version, relationship, found in, days overdue — instead of the detail blocks, so a large backlog stays readable.

Where Heeler knows the manifest file and line the dependency is declared on, the violation is also posted as an **inline annotation** on that file. Not every ecosystem supplies it; when it's missing, the finding still appears in the summary, just without an anchor.

{% hint style="info" %}
**Under Observe, nothing is posted.** Observe-action violations are recorded for measurement but excluded from the pull request report entirely. Check the **Pull Requests** and **Violations** tabs to see what an Observe-mode SLO guardrail is catching.
{% endhint %}

Under **Block**, the status check fails and — with branch protection enabled — the merge is held until the overdue work is fixed, or its deadline is formally extended. Actions and scope are the standard **Observe / Warn / Block** across the [scope model](/mrecEO40m5D6bt7Pq5pE/prevent/pr-guardrails.md#scope-where-each-guardrail-applies).

## Scoping it

The default **All Repositories (Global)** scope points this guardrail at your whole estate at once, which is almost never how you want to introduce it. The scopes that matter here:

* **Repository** — by name, source, or org/group. The narrowest way to pilot with one team before widening.
* **Service Runtime Context** — by tier, application, or internet accessibility. Block on production, internet-facing services; warn everywhere else.
* **Branch** — by **Branch Type**: **All**, **Default Branch**, or **All Except Default Branch**.

### Block the default branch, warn everywhere else

This is the scoping pattern most programs want, and it's the one that makes an SLO guardrail tolerable to live with. The **Branch** scope matches on the branch a pull request **targets** — its merge destination — which splits enforcement by where the change is trying to land:

| Guardrail   | Branch scope                  | Action    | Effect                                                                                  |
| ----------- | ----------------------------- | --------- | --------------------------------------------------------------------------------------- |
| The gate    | **Default Branch**            | **Block** | A PR into the default branch won't merge while the repository carries overdue findings. |
| The warning | **All Except Default Branch** | **Warn**  | A PR into any other branch reports the same overdue findings without failing the merge. |

Developers keep working — opening PRs, stacking branches, merging into release and integration branches — and they see the overdue debt on every one of those pull requests. What they can't do is land it on the default branch. Nobody is surprised at the merge, because they've been reading the same list on every PR that led up to it.

{% hint style="info" %}
**This takes two guardrails.** A guardrail carries one action, so the Block half and the Warn half are separate policies with the same rule and different branch scopes. Give them names that say which is which.
{% endhint %}

For the full operating model built on this pattern — the warning workflows that run ahead of it, the auto-fix that makes the deadline achievable, and the override valve that releases a team — see the [Vulnerability Jail](/mrecEO40m5D6bt7Pq5pE/solutions-and-use-cases/vulnerability-jail.md) playbook.

{% hint style="warning" %}
**"Default branch" means the branch Heeler tracks.** That's the repository's **scan branch override** where an operator has set one, and the SCM's default branch otherwise — not the literal name `main`. If a repository is tracked on a different branch, that branch is what **Default Branch** matches.

A pull request whose base branch Heeler can't resolve matches **neither** condition, so a branch-scoped guardrail doesn't evaluate it. Keep a **Global**-scoped Observe guardrail running if you want visibility into those.
{% endhint %}

## Notes and limits

* **It runs once per guardrail**, but violations are recorded **per code root** — a monorepo with several modules produces one execution row per module in the **Violations** tab.
* **A package resolving at several paths reports once.** A dependency pinned at one version can appear at many lockfile paths; Heeler reports each finding a single time.
* **Overdue is measured in whole days.** A finding one hour past a `0`-day threshold shows as `0` days overdue and still fires.
* **The check reflects the repository at the moment it ran.** Fixing an overdue finding elsewhere clears the block on the next evaluation, not retroactively on an already-reported check.

## Related

* [Service Level Objectives (SLOs)](/mrecEO40m5D6bt7Pq5pE/operate/service-level-objectives-slos.md) — how the deadlines this rule reads are set.
* [Set Program Policy → SLOs](/mrecEO40m5D6bt7Pq5pE/administer-and-monitor/program-policy/service-level-objectives.md) — configuring the strategy and the day windows.
* [Exceptions and Overrides](/mrecEO40m5D6bt7Pq5pE/operate/exceptions-and-overrides.md) — granting and reviewing extended deadlines.
* [Vulnerability Jail](/mrecEO40m5D6bt7Pq5pE/solutions-and-use-cases/vulnerability-jail.md) — the playbook this guardrail enforces: block the default branch, warn everywhere else.
* [Run an SLO-Driven AppSec Program](/mrecEO40m5D6bt7Pq5pE/solutions-and-use-cases/run-an-slo-driven-appsec-program.md) — the wider operating model this guardrail backstops.
* [Guardrail Types](/mrecEO40m5D6bt7Pq5pE/prevent/pr-guardrails/guardrail-types.md) — the other rule families and the shared scope model.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.heeler.com/mrecEO40m5D6bt7Pq5pE/prevent/pr-guardrails/guardrail-types/slo-guardrails.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
