# DB-backed Jira Internal Comment Idempotency

## Current Design

- `src/jira-comments.ts` creates Jira Service Management internal comments and updates Jira platform comments.
- The logical idempotency key identifies one automatic internal comment, for example `auto-live:v1:APID-29173:initial`.
- Duplicate detection currently scans Jira comment bodies for `auto-oncall-idempotency-key: <key>`.
- Visible comment bodies intentionally omit that marker, so comments created by the current writer cannot be found on a later run.
- `auto_oncall_runs.jira_internal_comment_json` records historical delivery results, but it is an immutable run audit record rather than a mutable idempotency mapping. Dry-run writes also do not create run rows.

## Risks

- Re-running the same logical analysis can create a second Jira internal comment.
- Reading historical run JSON as a live mapping would mix audit history with mutable delivery state.
- Falling back from a stale DB comment ID to a Jira body scan or a new POST could silently create duplicates.
- A Jira write followed by a process crash before the DB update remains an external-transaction boundary; the implementation must not claim exactly-once delivery across that crash window.

## Target Contract

- `auto_oncall_jira_internal_comments` is the canonical mapping from `idempotency_key` to Jira `comment_id` and `comment_url`.
- Both live worker writes and `/v1/tickets/analyze` dry-run writes resolve the target from this table before calling Jira.
- If the mapping has a comment ID, the writer performs only `PUT` against that ID.
- If the mapping does not exist, the writer performs one `POST`, then saves the returned ID in the mapping table.
- A stale/missing Jira comment for a mapped ID fails the write. It must not fall back to POST.
- Jira comment bodies contain no idempotency marker and are never scanned for one.
- Existing successful run JSON is used only to backfill the mapping during migration; it is not a runtime fallback.
- Preview mode does not read or write the mapping.
- Customer-visible replies are unchanged.

## Implementation Steps

1. Add the mapping table and idempotent historical backfill migration.
2. Add store methods to read and upsert the mapping by idempotency key.
3. Pass an explicit existing Jira comment ID into the Jira internal-comment writer.
4. Resolve and persist the mapping in both live-worker and dry-run-write orchestration.
5. Delete Jira-body marker lookup logic and update unit/fake-store contracts.

## Verification Checklist

- [x] Existing mapping causes Jira `PUT`, never `GET` body scan or `POST`.
- [x] Missing mapping causes Jira `POST` and persists its returned comment ID.
- [x] Repeated write reuses the persisted ID.
- [x] Empty/null historical comment IDs are not backfilled.
- [x] A stale mapped ID fails without creating a replacement comment.
- [x] Preview mode does not touch DB or Jira.
- [x] Live worker records the same comment ID in both the canonical mapping and run audit.
- [x] `npm run typecheck`, `npm test`, and `git diff --check` pass before deployment.
- [ ] Staging migration completes and APID-29173 updates comment `23137477` in place without creating another comment.

## Out of Scope

- No customer-visible Jira replies or issue transitions.
- No production rollout or production configuration change.
- No Jira-body compatibility fallback.
- No mutation of historical `auto_oncall_runs` rows after migration.
