Hamburger Cross Icon
Ticket Guardrails - Lunar Policy for Devex Build And Ci

Ticket Guardrails

✓ Policy Stable Devex Build And Ci

Enforce ticket hygiene across your organization. Verify PRs reference valid issue tracker tickets, check ticket status and type, enforce a specific tracker, and detect ticket reuse across multiple PRs. Works with Jira by default; other issue trackers require a compatible ticket collector.

Add ticket to your lunar-config.yml:
uses: github://earthly/lunar-lib/policies/ticket@v1.0.5

Included Guardrails

This policy includes 7 guardrails that enforce standards for your devex build and ci.

Guardrail

ticket-present

Ensures PRs reference an issue tracker ticket in the title.

ticket pr sdlc issue tracker
View Guardrail
Guardrail

ticket-valid

Ensures the referenced ticket exists and is valid in the issue tracker.

ticket validation issue tracker
View Guardrail
Guardrail

ticket-source

Ensures the ticket comes from an approved issue tracker.

ticket source jira linear issue tracker
View Guardrail
Guardrail

ticket-status

Ensures the ticket is in an acceptable workflow status.

ticket status workflow
View Guardrail
Guardrail

ticket-type

Ensures the ticket is an acceptable issue type.

ticket type issue type
View Guardrail
Guardrail

ticket-reuse

Prevents teams from recycling the same ticket across too many PRs.

ticket reuse compliance audit
View Guardrail
Guardrail

ticket-field

Ensures a configured ticket field, such as a Jira custom field, has an allowed value.

ticket field jira custom field approval issue tracker
View Guardrail

How Guardrails Fit into Lunar

Lunar guardrails define your engineering standards as code. They evaluate data collected by integrations and produce pass/fail checks with actionable feedback.

Policies support gradual enforcement—from silent scoring to blocking PRs or deployments—letting you roll out standards at your own pace without disrupting existing workflows.

Learn How Lunar Works →
1
↓ Integrations Gather Data
Collectors extract metadata from code, CI pipelines, tool outputs, and scans
2
{ } Centralized as JSON
All data merged into each component's unified metadata document
3
✓ Guardrails Enforce Standards This Policy
Real-time feedback in PRs and AI workflows

Required Integrations

This policy evaluates data gathered by one or more of the following integration(s). Make sure to enable them in your lunar-config.yml.

Configuration

Configure this policy in your lunar-config.yml.

Inputs

Input Required Default Description
allowed_sources Required — Comma-separated list of allowed ticket sources (e.g. "jira,linear"). Empty = any.
allowed_statuses Required — Comma-separated list of allowed ticket statuses (empty = any)
disallowed_statuses Required — Comma-separated list of disallowed ticket statuses (empty = none)
allowed_types Required — Comma-separated list of allowed issue types (empty = any)
max_ticket_reuse Optional 3 Maximum number of other PRs allowed to reference the same ticket
ticket_field Required — Field the `ticket-field` check reads, as a path relative to the ticket (e.g. native.jira.fields.customfield_10042 for a Jira custom field). Empty skips the check.
allowed_field_values Required — Values `ticket-field` accepts: comma-separated, one per line, or a JSON array for values that contain a comma. Empty accepts any value, so the field only has to be set.
ticket_path Optional .vcs.pr.ticket Where every check reads the ticket from. Point a second import of this policy at the path a second jira collector import records to (e.g. .vcs.pr.architecture_review) to check that reference as well.
skip_authors Required — PR author logins whose PRs every check skips, such as dependency bots (e.g. dependabot[bot], renovate[bot]). Comma-separated or one per line; matched exactly, ignoring case, so keep the [bot] suffix GitHub gives an app. Empty skips no one.

Documentation

View on GitHub

Ticket Guardrails

Enforce issue tracker ticket hygiene across your organization's pull requests. Works with any issue tracker (Jira, Linear, GitHub Issues, etc.).

Overview

This policy verifies that PRs reference valid tickets, checks ticket status and type, enforces a specific issue tracker, and detects ticket reuse across multiple PRs. It can also require any ticket field, such as a Jira custom field, to hold an allowed value, and run the same checks on a second reference like an architecture-review submission. It helps teams maintain traceability between code changes and project management.

Policies

This plugin provides the following policies (use include to select a subset):

Policy Description Failure Meaning
ticket-present PRs must reference a ticket No ticket ID found in PR title
ticket-valid Referenced ticket must exist Ticket ID was parsed but the tracker did not confirm it exists
ticket-source Ticket must come from an approved tracker Ticket source not in allowed list
ticket-status Ticket must be in an acceptable status Ticket status is disallowed or not in allowed list
ticket-type Ticket must be an acceptable issue type Issue type not in allowed list
ticket-reuse Same ticket can't be reused too many times Ticket used in more PRs than the configured limit
ticket-field A configured ticket field must have an allowed value Field is empty or its value is not in allowed_field_values

Required Data

This policy reads from the following Component JSON paths:

Path Type Description
.vcs.pr.ticket.id string Ticket ID extracted from PR title
.vcs.pr.ticket.source string Issue tracker name (e.g. "jira", "linear")
.vcs.pr.ticket.valid boolean Whether ticket exists in the tracker
.vcs.pr.ticket.tracker_error string Why .valid is absent, when the collector reports it (not_found, unreachable)
.vcs.pr.ticket.status string Ticket workflow status
.vcs.pr.ticket.type string Issue type (e.g. Story, Bug)
.vcs.pr.ticket.reuse_count number Count of other PRs using same ticket
.vcs.pr.ticket.native object Raw tracker data (e.g. .native.jira), read by ticket-field
.vcs.pr.author string PR author login, read only when skip_authors is set (github and gitlab collectors)

Every check reads from ticket_path instead of .vcs.pr.ticket when it is set.

Installation

Add to your lunar-config.yml:

policies:
  - uses: github://earthly/lunar-lib/policies/ticket
    on: ["domain:your-domain"]
    enforcement: report-pr
    include:
      - ticket-present
      - ticket-valid
      - ticket-source
      - ticket-status
      - ticket-type
      - ticket-reuse
      - ticket-field   # skips until ticket_field is set
    with:
      allowed_sources: "jira"
      disallowed_statuses: "Done,Closed"
      max_ticket_reuse: "3"
      # ticket_field: "native.jira.fields.customfield_10042"  # Optional: enables ticket-field

Skipping bot PRs

Bots such as Dependabot open PRs without a ticket. List their logins in skip_authors and every check skips their PRs instead of failing them:

    with:
      skip_authors: "dependabot[bot],renovate[bot]"

A login matches .vcs.pr.author exactly, ignoring case, so use the one GitHub shows, including an app's [bot] suffix. The skip covers the whole PR, including any commits someone else pushes to the bot's branch.

Checking a second reference

When a second jira collector import records another reference, such as an architecture-review submission at .vcs.pr.architecture_review, import the policy again with the same ticket_path. This one requires the PR to reference a submission that exists and is approved, and a custom field on it to be set:

policies:
  - uses: github://earthly/lunar-lib/policies/ticket
    name: architecture-review
    on: ["domain:your-domain"]
    enforcement: block-pr
    include: [ticket-present, ticket-valid, ticket-status, ticket-field]
    with:
      ticket_path: ".vcs.pr.architecture_review"
      allowed_statuses: "Approved"
      ticket_field: "native.jira.fields.customfield_10042"

ticket_field is relative to the ticket. A Jira select-list option matches on its value, a status or component on its name, and a user on its displayName; a multi-value field passes when any of its values is allowed. Jira lists custom field IDs at GET /rest/api/3/field.

Examples

Passing — Valid ticket with acceptable status

{
  "vcs": {
    "pr": {
      "ticket": {
        "id": "ENG-456",
        "source": "jira",
        "url": "https://acme.atlassian.net/browse/ENG-456",
        "valid": true,
        "status": "In Progress",
        "type": "Story",
        "summary": "Add payment validation",
        "assignee": "jane@acme.com",
        "reuse_count": 0
      }
    }
  }
}

Failing — No ticket in PR title

{}

Failure message: "PR does not reference a ticket. Include a ticket ID in the PR title (e.g. [ABC-123])."

Remediation

When this policy fails, you can resolve it by:

  1. ticket-present: Add a ticket reference to your PR title (e.g. ABC-123 Your PR description)
  2. ticket-valid: Verify the ticket ID exists in the issue tracker
  3. ticket-source: Use the approved issue tracker for your organization
  4. ticket-status: Move the ticket to an acceptable status before opening the PR
  5. ticket-type: Use an acceptable issue type (e.g. Story, Bug, Task)
  6. ticket-reuse: Create a new ticket for this work instead of reusing an existing one
  7. ticket-field: Set the field on the ticket to one of the allowed values

Open Source

This policy is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.

View Repository

Ready to Automate Your Standards?

See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 300+ built-in guardrails.

Works with any process
check AI agent rules & prompt files
check Post-mortem action items
check Security & compliance policies
check Testing & quality requirements
Auto-ID My Guardrails
Paste your AGENTS.md or manual process doc and get guardrails in minutes
Book a Demo