Ticket Valid + Jira Collector
Enforce Ticket Valid using data collected by Jira Collector. Automatically check devex build and ci standards on every PR.
How Jira Collector Powers This Guardrail
The Jira Collector gathers metadata from your vcs systems. This data flows into Lunar's Component JSON, where the Ticket Valid guardrail evaluates it against your standards.
When enabled, this check runs automatically on every PR and in AI coding workflows, providing real-time enforcement with actionable feedback.
Quick Start Configuration
Add both the collector and policy to your lunar-config.yml to enable this guardrail.
# Step 1: Enable the Jira Collector
collectors:
- uses: github://earthly/lunar-lib/collectors/jira@v1.0.5
# with: ...
# Step 2: Enable the Ticket Guardrails
policies:
- uses: github://earthly/lunar-lib/policies/ticket@v1.0.5
include: [ticket-valid]
# with: ...
What Jira Collector Collects
This collector gathers the following data that the Ticket Valid guardrail evaluates.
ticket
Extracts Jira ticket IDs from the PR title and description, takes the best one the Jira REST API confirms exists, and writes normalized ticket data to .vcs.pr.ticket and native Jira data to .vcs.pr.ticket.native.jira.
ticket-from-json
After-json variant of ticket: resolves the ticket from .vcs.pr (title
and description) in Component JSON (populated by the github or gitlab
collector) instead of calling the GitHub API, validates candidates against
the Jira REST API, and writes the same .vcs.pr.ticket (+ .vcs.pr.ticket.native.jira).
Works on any VCS provider that populates .vcs.pr and needs no GH_TOKEN.
Uses the experimental after-json hook; the code-hook ticket collector
above is kept for compatibility. Transitional: once after-json is no
longer experimental, this logic will be folded into the ticket
collector and ticket-from-json will be removed.
ticket-history
Queries the Lunar SQL database to count how many other PRs reference the same ticket. Resolves the ticket the same way the ticket collector does, so both agree on which one the PR is for. Writes .vcs.pr.ticket.reuse_count for policy evaluation.
Example Data Flow
Here's an example of the data that Jira Collector writes to the Component JSON, which Ticket Valid then evaluates.
{
"vcs": {
"pr": {
"ticket": {
"id": "ABC-123",
"source": { "tool": "jira", "integration": "api" },
"url": "https://acme.atlassian.net/browse/ABC-123",
"valid": true,
"status": "In Progress",
"type": "Story",
"summary": "Implement payment validation",
"assignee": "jane@acme.com",
"reuse_count": 0,
"native": {
"jira": { "...full Jira API response..." }
}
}
}
}
}
Configuration Options
Jira Collector Inputs
| Input | Required | Default | Description |
|---|---|---|---|
ticket_prefix |
Required | — | Character(s) before the ticket ID in the PR title or description (empty = match anywhere) |
ticket_suffix |
Required | — | Character(s) after the ticket ID in the PR title or description (empty = match anywhere) |
ticket_pattern |
Optional |
[A-Za-z][A-Za-z0-9]+-[0-9]+
|
Regex pattern for ticket ID (without prefix/suffix). The default accepts a project key of two or more characters, so XX-1 matches as well as ABC-123. It is permissive enough to also match tokens like UTF-8 or SHA-256; Jira does not know those, so they are skipped as candidates, but narrowing it to your project keys (e.g. (ABC|OPS)-[0-9]+) saves the lookups and is worth doing when Jira validation is not configured. |
ticket_keywords |
Optional |
fixes|fixed|fix|closes|closed|close|resolves|resolved|resolve|ticket|issue
|
Regex alternation of the keywords that mark a description reference as this PR's own ticket ("Fixes ABC-123", "Ticket: ABC-123"). Matched case-insensitively and ranked above any bare reference, so a dependency or related ticket mentioned earlier in the body does not win. Only applies to the description; the title is always matched bare. |
max_ticket_candidates |
Optional |
5
|
How many candidate keys to try against Jira before giving up. Candidates are ordered best-first, and each one costs a Jira lookup per sub-collector, so this bounds what a description full of key-shaped tokens can cost. |
jira_base_url |
Required | — | Jira instance base URL (e.g. https://acme.atlassian.net) |
jira_user |
Required | — | Jira user email for API authentication |
jira_retries |
Optional |
3
|
How many times to retry a Jira lookup that fails transiently — connection refused, timeout, 429, 5xx. A rejected credential or an unknown ticket is not retried. |
Ticket Guardrails 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 |
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 200+ built-in guardrails.