How Do I Catch Duplicate Tickets Before Dispatch?
A runnable ticket sweep that separates fully covered, related, and distinct work before creation.

Problem: Why do duplicate tickets survive until dispatch?
A duplicate survives when a title search substitutes for reading the intended outcome. A sign-out report and a request to restore sessions can authorize the same repair under different titles and states.
The primary control belongs before creation: search New, Ready, Active, and Blocked in every target queue, read every plausible match, and classify the request. The portable lab below also handles a duplicate that already exists by preserving and linking it, removing its dispatch authority, extending the canonical ticket, and verifying both records. A focused dispatch recheck catches later changes.
Value: What does a pre-create sweep protect?
The sweep protects one accountable ticket for one intended outcome. Covered work stays with its acceptance criteria; related work keeps a visible dependency; distinct work carries the search that justified creation. The record saves the next operator from reconstructing the decision and can prevent repeated implementation, review, compute, and cleanup. It does not promise a universal saving or detection rate.
Need help turning this pattern into a dependable AI system?
Just In Time AI can help your team design and implement the smallest useful workflow with the security, compliance, and operational controls it needs, then prove it before expanding. Explore AI Systems Setup and Coaching.
Solution: How do I run the sweep from true step one?
What do I need before I start?
Set aside 15 to 30 minutes. You need:
- Complete read scope: Read New, Ready, Active, and Blocked in every target project or queue.
- Authorized write scope: Confirm who may update, resolve, create, and dependency-link tickets.
- A local runtime: Use Python 3.10 or later for the offline lab.
- A precise request: State the symptom, component, and intended outcome.
If access omits a queue or state, request it; incomplete results cannot support distinct or none-found. The lab performs no live mutation.
Step 0 -- How do I verify the runtime before exporting tickets?
Allow five minutes when Python and Bash are present, or 15 to 30 minutes to install either. Save runtime-check.sh:
#!/usr/bin/env bash
set -euo pipefail
python_bin="${PYTHON_BIN:-python3}"
"$python_bin" - <<'PY'
import sys
if sys.version_info < (3, 10):
raise SystemExit("runtime check requires Python 3.10 or later")
print(
"runtime_check=PASS "
f"python={sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
)
PY
printf 'bash_version=%s\n' "$BASH_VERSION"
Run PYTHON_BIN=python3 bash runtime-check.sh. On 2026-09-14, I used PYTHON_BIN=/opt/homebrew/bin/python3.13 bash runtime-check.sh and received:
runtime_check=PASS python=3.13.13
bash_version=3.2.57(1)-release
If Python is older than 3.10, use Python.org. If Bash is absent, use the GNU Bash project or your supported package source. Do not export until both respond.
Step 1 -- How do I prepare a safe ticket export?
Export the four states into local JSON. Name the target lane, project, queue, requested outcome, and overlap rationale. Retain only required IDs, states, titles, and descriptions; remove customer names, secrets, tokens, and unrelated notes before sharing.
For the lab, save this as tickets.json:
{
"target": {
"lane": "identity-operations",
"project": "customer-portal",
"queue": "authentication-reliability"
},
"request": {
"requested_outcome": "Restore a user's existing session after a transient identity-service timeout.",
"rationale": "A new incident report says users are signed out after the same timeout, so active recovery work may already authorize the outcome."
},
"required_states": ["New", "Ready", "Active", "Blocked"],
"tickets": [
{
"id": "LAB-A",
"state": "Active",
"title": "Recover sessions after an auth timeout",
"description": "Restore the existing user session after a transient identity-service timeout."
},
{
"id": "LAB-B",
"state": "Ready",
"title": "AUTHENTICATION timeout telemetry",
"description": "Record timing evidence needed by session recovery without changing recovery behavior."
},
{
"id": "LAB-C",
"state": "Blocked",
"title": "Prevent SIGN-OUT during identity retry",
"description": "Preserve the session while the identity client retries a transient timeout."
},
{
"id": "LAB-D",
"state": "New",
"title": "Preserve user continuity",
"description": "Restore a user's session after an authentication timeout."
},
{
"id": "LAB-E",
"state": "Ready",
"title": "Update login page spacing",
"description": "Adjust form spacing and typography; no functional behavior changes."
}
]
}
Verify all four states appear. Repair omissions before searching.
Step 2 -- How do I search titles and descriptions without regard to case?
Save ticket_sweep.py beside the fixture. It rejects missing states and one-term searches, then verifies the post-write readback before success. One generic term rarely expresses an outcome, and a successful mutation response does not prove the duplicate lost dispatch authority.
#!/usr/bin/env python3
"""Find candidate tickets across the four required pre-create states."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
REQUIRED_STATES = ("New", "Ready", "Active", "Blocked")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("ticket_file", type=Path)
parser.add_argument("--terms", required=True, help="comma-separated search terms")
parser.add_argument(
"--after-readback",
type=Path,
help="optional post-write export used to verify the canonical and duplicate tickets",
)
args = parser.parse_args()
data = json.loads(args.ticket_file.read_text(encoding="utf-8"))
target = data.get("target", {})
request = data.get("request", {})
if any(not target.get(field) for field in ("lane", "project", "queue")):
raise SystemExit("ticket export must identify the target lane, project, and queue")
if any(not request.get(field) for field in ("requested_outcome", "rationale")):
raise SystemExit("ticket export must identify the requested outcome and rationale")
if tuple(data.get("required_states", ())) != REQUIRED_STATES:
raise SystemExit("ticket export must include New, Ready, Active, and Blocked")
terms = tuple(term.strip().casefold() for term in args.terms.split(",") if term.strip())
if len(terms) < 2:
raise SystemExit("use at least two symptom, component, or outcome terms")
candidates = []
for ticket in data["tickets"]:
if ticket["state"] not in REQUIRED_STATES:
continue
title = ticket["title"].casefold()
description = ticket["description"].casefold()
matches = [term for term in terms if term in title or term in description]
if matches:
candidates.append((ticket, matches, not any(term in title for term in terms)))
readback = None
if args.after_readback is not None:
after = json.loads(args.after_readback.read_text(encoding="utf-8"))
if after.get("target") != target or after.get("request") != request:
raise SystemExit("post-write readback must preserve the exact target and request")
readback = after.get("readback", {})
canonical_id = readback.get("canonical_ticket_id")
duplicate_id = readback.get("duplicate_ticket_id")
incident_evidence = readback.get("incident_evidence")
after_tickets = {ticket["id"]: ticket for ticket in after.get("tickets", [])}
canonical = after_tickets.get(canonical_id, {})
duplicate = after_tickets.get(duplicate_id, {})
if not canonical_id or not duplicate_id or canonical_id == duplicate_id:
raise SystemExit("post-write readback must identify distinct canonical and duplicate tickets")
if (
not incident_evidence
or canonical.get("state") != "Active"
or incident_evidence not in canonical.get("evidence", [])
or canonical.get("dispatch_authority") is not True
):
raise SystemExit(
"post-write canonical ticket must remain Active, retain dispatch authority, and contain the new incident evidence"
)
if (
duplicate.get("state") != "Closed"
or duplicate.get("resolution") != "Duplicate"
or duplicate.get("duplicate_of") != canonical_id
or duplicate.get("dispatch_authority") is not False
):
raise SystemExit(
"post-write duplicate ticket must be closed, marked Duplicate, linked to the canonical ticket, and stripped of dispatch authority"
)
print(
f"target_lane={target['lane']} project={target['project']} "
f"queue={target['queue']}"
)
print(f"requested_outcome={request['requested_outcome']}")
print(f"request_rationale={request['rationale']}")
print("states=" + ",".join(REQUIRED_STATES))
for ticket, matches, body_only in candidates:
print(
f"candidate={ticket['id']} state={ticket['state']} "
f"matches={','.join(matches)} body_only={str(body_only).lower()}"
)
print(f"candidate_count={len(candidates)} read_required={len(candidates)}")
if readback is not None:
canonical = after_tickets[readback["canonical_ticket_id"]]
duplicate = after_tickets[readback["duplicate_ticket_id"]]
print(
f"readback={canonical['id']} state={canonical['state']} "
"incident_evidence=true dispatch_authority=true"
)
print(
f"readback={duplicate['id']} state={duplicate['state']} "
f"resolution={duplicate['resolution']} duplicate_of={duplicate['duplicate_of']} "
"dispatch_authority=false"
)
print(
f"readback_result=PASS canonical_ticket={canonical['id']} "
f"duplicate_ticket={duplicate['id']}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
Use this runner so the script and fixtures resolve from any directory:
#!/usr/bin/env bash
set -euo pipefail
asset_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
python_bin="${PYTHON_BIN:-python3}"
"$python_bin" "$asset_dir/ticket_sweep.py" "$asset_dir/tickets.json" \
--terms "sign-out,session,authentication,timeout" \
--after-readback "$asset_dir/tickets-after.json"
Pass the runner's path to Bash. Expect twelve lines:
target_lane=identity-operations project=customer-portal queue=authentication-reliability
requested_outcome=Restore a user's existing session after a transient identity-service timeout.
request_rationale=A new incident report says users are signed out after the same timeout, so active recovery work may already authorize the outcome.
states=New,Ready,Active,Blocked
candidate=LAB-A state=Active matches=session,timeout body_only=false
candidate=LAB-B state=Ready matches=session,authentication,timeout body_only=false
candidate=LAB-C state=Blocked matches=sign-out,session,timeout body_only=false
candidate=LAB-D state=New matches=session,authentication,timeout body_only=true
candidate_count=4 read_required=4
readback=LAB-A state=Active incident_evidence=true dispatch_authority=true
readback=LAB-D state=Closed resolution=Duplicate duplicate_of=LAB-A dispatch_authority=false
readback_result=PASS canonical_ticket=LAB-A duplicate_ticket=LAB-D
Illustrative record LAB-D matches only through its description. LAB-E stays out because its interface change shares neither symptom nor outcome.
Step 3 -- How do I read every hit and classify the relationship?
Read each candidate's outcome, criteria, evidence, dependencies, and state. Record one disposition:
| Disposition | Test | Required action |
|---|---|---|
| Fully covered | The existing ticket already requires the complete new outcome. | Preserve any later duplicate, mark it Duplicate, link it to the canonical ticket, close it or otherwise remove its independent dispatch authority, verify that state, and then extend the canonical ticket with the new evidence. Create nothing. |
| Related | The outcomes are distinct, but one constrains or must precede the other. | Create only the distinct outcome and add a visible dependency. |
| Distinct | No plausible ticket covers or constrains the outcome after every candidate is read. | Create the ticket with the search and none-found rationale. |
LAB-A covers restoration. LAB-B and LAB-C are related telemetry and retry work. LAB-D repeats LAB-A; leaving it New preserves a second dispatch authorization. Words identify what to read, never the disposition.
Step 4 -- How do I act without losing the sweep evidence?
CAUTION -- changing the wrong record can suppress distinct work or leave a duplicate authorized: Route the proposed illustrative IDs and disposition to the customer-portal request owner through the owner-visible identity-operations / customer-portal / intake-decisions queue. For this lab, require a response by 2026-09-15 at 5:00 p.m. America/New_York. The default on silence is BLOCKED -- NO MUTATION. An unstated silence path either stalls forever or lets an operator mistake no answer for self-authorization. Using the wrong ID could close canonical record LAB-A while duplicate LAB-D remains dispatchable; a wrong disposition could suppress distinct telemetry work or authorize the recovery twice.
- For fully covered work with no later duplicate ticket: Append the new reproduction evidence and request context to the canonical ticket. Create nothing.
- For fully covered work with a later duplicate: Preserve its history. Mark it Duplicate, link it to the canonical ticket, close it or remove its dispatch authority through the approved tracker action, and read it back. Then extend and reread the canonical ticket.
- For related work: Create the distinct outcome, attach the sweep evidence, and add the tracker's explicit dependency from the new ticket to its prerequisite.
- For distinct work: Create the ticket with the empty candidate list or the read-and-rejected candidates and the
none-foundrationale.
Verify LAB-D is Closed, marked Duplicate, linked to LAB-A, and cannot dispatch. Then verify LAB-A remains Active, retains authority, and contains the evidence. For related or distinct work, verify the dependency or rationale.
Save the inspectable post-write states as tickets-after.json:
{
"target": {
"lane": "identity-operations",
"project": "customer-portal",
"queue": "authentication-reliability"
},
"request": {
"requested_outcome": "Restore a user's existing session after a transient identity-service timeout.",
"rationale": "A new incident report says users are signed out after the same timeout, so active recovery work may already authorize the outcome."
},
"readback": {
"canonical_ticket_id": "LAB-A",
"duplicate_ticket_id": "LAB-D",
"incident_evidence": "Incident reports user sign-out after a transient identity-service timeout."
},
"tickets": [
{
"id": "LAB-A",
"state": "Active",
"title": "Recover sessions after an auth timeout",
"description": "Restore the existing user session after a transient identity-service timeout.",
"evidence": ["Incident reports user sign-out after a transient identity-service timeout."],
"dispatch_authority": true
},
{
"id": "LAB-D",
"state": "Closed",
"title": "Preserve user continuity",
"description": "Restore a user's session after an authentication timeout.",
"resolution": "Duplicate",
"duplicate_of": "LAB-A",
"dispatch_authority": false
}
]
}
When creation is justified, carry SWEEP_EVIDENCE="lanes=... kw=... found=#id(disposition)...". Keep the project, queue, outcome, rationale, candidate readbacks, and decision in the attached record; the compact line only indexes that proof.
Step 5 -- How do I use dispatch as the final backstop?
Immediately before assignment, rerun the focused search and read any new candidates. A changed outcome, reopened ticket, new plausible match, or missing dependency invalidates the earlier disposition. Replace the stale evidence before dispatch.
How do I validate the sweep and recover from mistakes?
Confirm PASS and a Bash version, then byte-compare the twelve sweep lines with expected-output.txt. Test three failures: one term must report use at least two symptom, component, or outcome terms; an omitted state must report ticket export must include New, Ready, Active, and Blocked; and tickets-after-invalid.json must report that the duplicate must be closed, linked, and stripped of dispatch authority.
Practitioner note -- 2026-09-14: I ran these exact fixtures from an unrelated temporary directory with Python 3.13.13 and Bash 3.2.57. Runtime matched two lines; the sweep matched twelve. The one-term, missing-state, still-dispatchable-duplicate, and missing-canonical-evidence negatives each stopped correctly. This was a local illustrative lab, not a live tracker or customer deployment.
When results look wrong:
- An obvious ticket is missing: Confirm all four states were exported, search a known phrase in different case, and check that descriptions are present. Repair the input before recording
none-found. - Too many false matches appear: Replace generic terms such as "fix" with the symptom, component, and intended outcome. Read every retained candidate.
- The script rejects the export: Add the missing state, even if its ticket list is empty, and rerun. The four-state declaration makes the search boundary inspectable.
- The write landed on the wrong ticket: Stop dispatch, use the tracker's approved correction or reversal path, preserve the audit history, and rerun the sweep. Never delete history to hide the mistake.
- The dependency is absent or reversed: Keep the new ticket out of dispatch, correct the relationship, and verify both tickets again.
Artifacts
- Python and GNU Bash -- sources for a missing runtime prerequisite.
- Ticket sweep before create -- the current four-state search, full-read, carrier, and extend-or-link contract.
- Verify Before Claiming Missing or Broken -- the current evidence-first search and inspection rule.
- runtime-check.sh and runtime-expected-output.txt -- runtime validation and its exact verified result.
- tickets.json and ticket_sweep.py -- the pre-write fixture and candidate search.
- run-sweep.sh and expected-output.txt -- the location-independent runner and its exact output.
- tickets-after.json and tickets-after-invalid.json -- the valid readback and still-dispatchable negative control.
- sweep-evidence.json -- the target, request, approval route, dispositions, actions, source hashes, and final readbacks.
For the business decision and value behind this method, read Why Does One Bug Filed as Two Tickets Get Fixed Twice?.
Bottom Line: What evidence should I keep before dispatch?
Keep one inspectable record with the exact search target, requested outcome, rationale, every candidate read and disposition, final action, and actual readback result:
{
"target": {
"lane": "identity-operations",
"project": "customer-portal",
"queue": "authentication-reliability"
},
"requested_outcome": "Restore a user's existing session after a transient identity-service timeout.",
"request_rationale": "A new incident report says users are signed out after the same timeout, so active recovery work may already authorize the outcome.",
"states": ["New", "Ready", "Active", "Blocked"],
"terms": ["sign-out", "session", "authentication", "timeout"],
"source_bindings": {
"runtime_check": {
"path": "runtime-check.sh",
"sha256": "5d13ae80e2ac2c32bf8c559e1402607c880ab989f8a622841899fbf406af0911"
},
"runtime_verified_output": {
"path": "runtime-expected-output.txt",
"sha256": "02132995ea24eff9a3652d5665cd93787824bea8373d0e9090f43d25ff011f96"
},
"pre_write_ticket_export": {
"path": "tickets.json",
"sha256": "ec96a25687a8330c39b5fac3a1c4b5f28928a487f3a474fea2d73a24bb7b10fb"
},
"post_write_readback": {
"path": "tickets-after.json",
"sha256": "8ba43c58e1dc568478e75bdee6a28131a7ed4869c9a077843bc4b2277f261a66"
},
"verified_output": {
"path": "expected-output.txt",
"sha256": "3ab687a820a5cf07154e27ecb492fde60a4866cdd1a857e2ded04fabb673019f"
}
},
"mutation_approval": {
"accountable_owner": "customer-portal request owner",
"owner_visible_queue": "identity-operations / customer-portal / intake-decisions",
"response_deadline": "2026-09-15T17:00:00-04:00",
"default_on_silence": "BLOCKED_NO_MUTATION",
"default_rationale": "Without a declared silence path, the request can stall indefinitely or an operator can mistake no response for permission. Silence therefore cannot authorize a ticket mutation.",
"wrong_action_consequence": "Using the wrong illustrative ID could close canonical record LAB-A while leaving duplicate LAB-D dispatchable. Misclassifying related telemetry as fully covered could suppress distinct work; misclassifying covered recovery as related could authorize the same recovery twice."
},
"candidate_reads": [
{
"ticket_id": "LAB-A",
"actual_readback_result": "Active ticket requires restoring the existing user session after a transient identity-service timeout.",
"disposition": "fully-covered",
"rationale": "Its required outcome includes the complete requested result."
},
{
"ticket_id": "LAB-B",
"actual_readback_result": "Ready ticket records authentication-timeout telemetry and explicitly does not change recovery behavior.",
"disposition": "related",
"rationale": "Its evidence can inform recovery, but its intended outcome is distinct."
},
{
"ticket_id": "LAB-C",
"actual_readback_result": "Blocked ticket preserves the current session during identity-client retry.",
"disposition": "related",
"rationale": "Preserving a live session during retry constrains recovery but does not restore a lost session."
},
{
"ticket_id": "LAB-D",
"actual_readback_result": "New ticket requires restoring a user's session after an authentication timeout.",
"disposition": "fully-covered",
"rationale": "It repeats the requested outcome and is itself covered by the older accountable record LAB-A."
}
],
"sweep_evidence": "SWEEP_EVIDENCE=\"lanes=identity-operations kw=sign-out,session,authentication,timeout found=LAB-A(fully-covered),LAB-B(related),LAB-C(related),LAB-D(fully-covered)\"",
"final_decision": {
"disposition": "fully-covered",
"create": "do-not-create",
"resolve_duplicate": "mark-LAB-D-as-duplicate-of-LAB-A-and-close-it",
"remove_dispatch_authority": "LAB-D-dispatch-authority-false",
"extend": "extend-LAB-A-with-new-incident-evidence",
"link": "LAB-D-duplicate-of-LAB-A",
"sequence": "Preserve illustrative record LAB-D, mark it Duplicate, link it to LAB-A, close it, verify that it cannot dispatch independently, then append the incident evidence to LAB-A.",
"rationale": "LAB-A already authorizes the complete requested outcome; LAB-B and LAB-C remain distinct related work, and LAB-D duplicates LAB-A.",
"actual_readback_result": "LAB-D is preserved as Closed with resolution Duplicate, links to LAB-A, and has no dispatch authority. LAB-A remains Active, contains the new incident evidence, and is the only record authorized to dispatch the recovery work."
},
"post_write_readbacks": [
{
"ticket_id": "LAB-D",
"state": "Closed",
"resolution": "Duplicate",
"duplicate_of": "LAB-A",
"dispatch_authority": false
},
{
"ticket_id": "LAB-A",
"state": "Active",
"incident_evidence": "Incident reports user sign-out after a transient identity-service timeout.",
"dispatch_authority": true
}
]
}
Do this before the next ticket is created:
- Reproduce the lab: Copy the files and compare the runner's twelve output lines with the expected output, including both post-write readbacks.
- Run a read-only sweep: Search one project or queue you are authorized to inspect, then read every candidate.
- Save the disposition: Record the search, candidate reads, decision, action, and readback. If a duplicate already exists, preserve it, record its canonical link, and prove it cannot dispatch independently before creating or dispatching work.
This protects engineering time and AI spend for new outcomes instead of work already authorized.
Share this line with the operator who owns intake: "Search four states, read every hit, and prove the disposition before you create."
If a description search exposes a ticket the title search missed, record the term that found it in the sweep evidence. You can also send the result through DanStolts.com.
Frequently Asked Questions
Can a matching ticket title prove that work is fully covered?
No. A matching title identifies a candidate. Read the description, complete intended outcome, acceptance criteria, evidence, dependencies, and current state. Classify the request as fully covered only when the existing ticket already requires the complete requested outcome.
Does a Blocked ticket still count as a duplicate-ticket candidate?
Yes. Blocked work can fully cover the requested outcome, or its unresolved dependency can make the new ticket related. The Blocked state changes readiness; it does not make duplicate creation safe.
When do two distinct tickets require a dependency?
Add a dependency when both tickets require distinct outcomes but one constrains, supplies evidence for, or must finish before the other. Preserve both tickets and make the direction visible. If one ticket already contains the complete requested outcome, classify the request as fully covered instead.
What should I do when a duplicate ticket already exists?
Preserve the later ticket and its history. Mark it Duplicate, link it to the canonical ticket, close it or use the tracker's equivalent state that removes independent dispatch authority, and verify that result. Then add the new evidence to the canonical ticket and verify that ticket as well.
What evidence must I save after a duplicate-ticket sweep?
Save the exact lane, project, queue, requested outcome, rationale, searched states and terms, every candidate's actual readback and disposition, and the final create/extend/link decision with its readback. When creation is justified, pass SWEEP_EVIDENCE="lanes=... kw=... found=#id(disposition)..."; that compact carrier indexes the proof but does not replace the source tickets or saved search output.
How often should I rerun a duplicate-ticket sweep?
Run it before every ticket creation. Rerun it when the requested outcome, candidate body, state, dependency, acceptance criteria, evidence, or available records change materially. Run a focused recheck immediately before dispatch.
Can I automate the duplicate-ticket decision?
Automate collection, normalization, and candidate ranking first. Keep the final fully-covered, related, or distinct decision inspectable because titles and similarity scores cannot prove outcome coverage. Let automation mutate or close tickets only after an authorized decision names the exact records and the system verifies the resulting state.
Update History
- 2026-09-14 - Reader path and implementation help: Added the property-specific Just In Time AI service callout within the first third of the article and verified the paired reader path. The lifecycle-authorized article is bound to its locked canonical route; deployment and anonymous readback remain pending.
Evidence and provenance: This lifecycle-authorized tutorial is bound to its locked canonical route; deployment and anonymous public readback remain pending. The five-ticket data set, post-write readback, and resulting sweep record are illustrative fixtures, not a customer or deployment record. The practiced method is bound to Ticket sweep before create at https://github.com/dstolts/jitneuro/blob/6f98de1fd0272f00d9e4ade48f6cd5771786f035/AGENTS.md and Verify Before Claiming Missing or Broken at https://github.com/dstolts/jitneuro/blob/6f98de1fd0272f00d9e4ade48f6cd5771786f035/templates/rules/verify-before-claiming.md. Those sources support the search-read-disposition sequence and qualitative duplicate-work risk, not measured savings, complete detection, or a customer outcome. The runtime, runner, and four deliberate failures were validated locally on 2026-09-14.
Update history
What changed: Replaced illustrative numeric ticket strings with neutral LAB identifiers, refreshed their runnable evidence bindings, and prepared the approved revision for a fresh, SHA-bound review cycle.
Why it matters: Preserves the tutorial, locked 2020-03-17 practice date, evidence boundary, and pair relation while preventing the fixture from being mistaken for live work.
What changed: Replaced the dispatch-only baseline with a runtime-verified pre-create sweep, complete duplicate lifecycle, explicit owner decision route, hash-bound post-write readback, and failure controls.
Why it matters: Lets a builder prove that a duplicate cannot dispatch independently and that the canonical ticket holds the new evidence.