Outcome
By the end of this lesson, you will be able to trace status and ownership changes in both directions, explain how the loop guard prevents ping-pong, and name the deferred limits.
| Tier | Engineer |
| JTBD | ”Stop reconciling ticket status against recommendation status by hand.” |
| Personas | Platform Engineer · SRE · FinOps Lead |
| Prerequisites | M5.9.L3 |
| Time | 9 minutes |
| Bloom verb | Trace (Analyze), Explain (Understand), Name (Remember) |
1. Concept
Recommendation status and Jira issue status stay in agreement automatically, and it is event-driven: there is no polling cron.
Outbound: ZopNight to Jira
The recommender emits a status-changed event at itstransition chokepoints. The provisioner transitions orcomments the Jira issue.
closed / dismissed / optimised -> Done applied / observing -> In Progress drifted -> a COMMENTThe drifted case is deliberately a comment rather than a transition. Drift means the finding came back, and forcing an issue backwards through a workflow is presumptuous: the comment tells the assignee and lets them decide.
Inbound: Jira to ZopNight
A durable Jira admin webhook, HMAC-verified perintegration, with retries deduplicated on Atlassian'sown delivery identifier.
The org and recommendation are resolved by theGLOBALLY-UNIQUE FINGERPRINT LABEL (L2).
It publishes an event; the recommender applies thetarget status through its state machine.
ILLEGAL TRANSITIONS ARE DROPPED, NEVER FORCED.That last line preserves an invariant from T2.M2.13: the recommender remains the sole writer of recommendation status. Jira can request a transition; it cannot impose one that the state machine does not permit.
The loop guard
Two systems that write to each other will ping-pong unless something stops them.
ZopNight sets status -> pushes to Jira -> Jira emits achange event -> ZopNight receives it -> sets status ->pushes to Jira -> ...
THE GUARD keys on the link's LOCALE-INDEPENDENT NEUTRALSTATUS CATEGORY plus the LAST-PUSHED recommendationstatus.
So an inbound event that merely reflects what we justpushed is recognised and dropped.The locale-independent part is worth noticing. Jira status names are localised, so a guard comparing display strings would break on a Jira instance running in another language. Comparing the neutral category rather than the label is what makes it work anywhere.
The whole thing is idempotent under at-least-once delivery, which it needs to be: Jira retries webhooks, and the dedup on the delivery identifier plus the loop guard together make reprocessing harmless.
Ownership sync, both directions
ZOPNIGHT -> JIRA Set an owner on a recommendation, and it updates the ticket link and RE-ASSIGNS THE SAME ISSUE. It NEVER MINTS A NEW TICKET.
JIRA -> ZOPNIGHT A Jira assignee change flows back onto the recommendation's owner via the inbound webhook. Prefers email; falls back to display name where privacy settings hide the email; clears on unassign.
Both directions are loop-guarded.The “never mints a new ticket” guarantee matters: changing an owner is an update to an existing thing, and a system that created a second ticket on reassignment would double its own queue.
A deleted Jira issue
SOFT-DELETES THE LINK. THE RECOMMENDATION IS UNTOUCHED.The right behaviour: someone deleting a ticket is saying something about their queue, not about your infrastructure. The finding remains open in ZopNight, where it belongs.
The deferred limits
Worth knowing so you do not design around something absent:
SINGLE SHARED WEBHOOK HMAC SECRET A per-integration secret is a hardening follow-up.
NO JQL SEARCH-BEFORE-CREATE Dedup is on the fingerprint, not a search of your Jira project. A ticket someone created by hand, without the label, is not found.
NO DAILY BACKSTOP SWEEP Jira's own webhook retries plus the idempotent reprocessing carry it. If a webhook is lost past those retries, that change is not reconciled later.
NO JIRA-REOPEN -> RECOMMENDATION-REOPEN Reopening a Jira issue does not reopen the recommendation.
NO ACTIVITY-EVENTS TABLE, and observability countersare follow-ups.The second and fourth are the ones that show up in practice.
NO SEARCH-BEFORE-CREATE means a hand-made ticketwithout the fingerprint label is invisible to dedup, soautomation can open a second one for the same problem.
If your team creates cost tickets by hand today, eitherroute them through the integration or accept someoverlap during transition.2. Demo
A quarter of sync, including the two things that surprised people:
NORMAL FLOW A rec is applied in ZopNight -> Jira issue moves to In Progress Verification later confirms adoption; rec closes -> issue moves to Done Nobody touched Jira. That is the point.
INBOUND, WORKING An engineer closes the Jira issue as Done from their queue. -> webhook fires, HMAC verified -> org and rec resolved from the fingerprint label -> the recommender applies the status through its state machine
No ping-pong. The loop guard recognised the subsequent outbound push as reflecting what had just arrived.
SURPRISE 1: A LOCALISED JIRA A subsidiary ran Jira in German. Status labels came through localised.
Sync worked. The loop guard keys on the NEUTRAL STATUS CATEGORY rather than the display label, which is exactly the case that would have broken a string-comparison guard.
SURPRISE 2: A REOPENED TICKET An engineer reopened a Done issue, expecting the recommendation to reopen too.
It did not. Jira-reopen to recommendation-reopen is DEFERRED.
The right mental model: the recommender is the sole writer of recommendation status, and it reopens a finding when the RULE FIRES AGAIN, not when a human reopens a ticket. Jira can request transitions the state machine permits; it cannot substitute for detection.
THE HAND-MADE TICKET OVERLAP During transition, the team had 11 hand-made cost tickets with no fingerprint label.
Auto-ticketing opened new tickets for 6 of those problems, because dedup is on the fingerprint and there is no JQL search-before-create.
Not a bug, and they had been told. They closed the 6 hand-made duplicates and routed everything through the integration afterwards.
AN ASSIGNEE ROUND TRIP Rec owner set in ZopNight -> the SAME issue reassigned, no new ticket. A week later the Jira assignee changed during a handover -> the rec's owner updated to match. Loop-guarded in both directions.3. Hands-on (6 min)
1. Apply a recommendation that has a linked ticket. Did the issue move to In Progress? Y / N How long did it take? ______ seconds (Event-driven, so seconds rather than a cron cycle.)
2. Close the Jira issue from Jira. Did the rec status follow? Y / N Any ping-pong? Y / N
3. Reopen that Jira issue. Did the rec reopen? Y / N Explain why not, in one line: ______________________________________________
4. Count your hand-made cost tickets that predate the integration: ______ Those carry no fingerprint label. What will you do? [ ] close and re-route [ ] accept overlap
5. If you run Jira in a non-English locale, confirm sync works: Y / N (The guard uses the neutral category, not labels.)4. Knowledge check
Q1
What prevents recommendation status and Jira status from ping-ponging endlessly?
A. A cooldown period after each push
B. Outbound sync is disabled the moment inbound sync becomes active, so only one direction is ever live at a time and a loop is impossible by construction in the design itself entirely
C. Jira suppresses webhook events triggered by API calls
D. A loop guard keyed on the link’s locale-independent neutral status category plus the last-pushed recommendation status, so an inbound event that merely reflects what was just pushed is recognised and dropped
Show answer
Correct: D. Using the neutral category rather than display labels is what keeps it working on a Jira instance in another language. The localisation detail is the one that would break a naive implementation: status names are localised, so a guard comparing display strings would fail on a non-English Jira.
Q2
An engineer reopens a Done Jira issue. The linked recommendation:
A. Does not. Jira-reopen to recommendation-reopen is deferred
B. Reopens automatically
C. Reopens after the next evaluation cycle
D. Reopens only if the rec was closed by sync rather than by verification
Show answer
Correct: A. The recommender is the sole writer of recommendation status and reopens a finding when the rule fires again, so Jira can request transitions the state machine permits but cannot substitute for detection. The same invariant explains why illegal transitions arriving from Jira are dropped rather than forced: an external system requests, and the state machine decides.
Q3
A team has hand-made cost tickets predating the integration. When auto-ticketing is enabled:
A. Those tickets are found and reused
B. The hand-made tickets are labelled retroactively
C. The integration refuses to open tickets in any project that already contains untagged issues created by hand previously
D. New tickets may be opened for the same problems, because dedup is on the fingerprint label and there is no JQL search-before-create
Show answer
Correct: D. A hand-made ticket without the label is invisible to dedup. This is a known deferred limit rather than a defect, and it is worth planning for: either route existing tickets through the integration or accept some overlap during the transition and close the duplicates.
5. Apply
Test the round trip once in each direction before relying on it: apply a recommendation and watch the issue move, then close the issue and watch the recommendation follow. Seeing both work is what lets you stop reconciling by hand.
Deal with hand-made tickets before enabling auto-ticketing, since they carry no fingerprint and will not deduplicate.
Related lessons
- L3: Policy-gated auto-ticketing
- L2: A ticket from a recommendation
- T2.M2.13.L2: Status is a claim, evidence is a fact
- T2.M2.13.L5: Rule versions, retirement and supersede
Glossary terms touched
Loop guard · Neutral status category · HMAC verification · Ownership sync