[enhancement] Programmatic ticket closure for CC agent (Resolved + Won't Do + Duplicate) #10

Closed
opened 2026-07-07 10:52:17 -04:00 by jmcqueen · 1 comment
Owner

Extend the app so the Contact Center AI agent can programmatically close SS tickets end-to-end — the natural next step after ticket creation lands. Design informed by direct Jira workflow probing.

Workflow reality check

The Resolved transition (id 161) in project SS has:

  • 1 field on the transition screen: resolution (8 valid values: Done, Won't Do, Duplicate, Fixed, Cannot Reproduce, Blocked, Invalid, Declined)
  • 4 fields checked by a workflow validator (NOT on the transition screen — must already be set on the ticket before you call the transition):
    • components — Jira native, 63 fixed options
    • customfield_10224 Business Service — Assets CMDB, objectTypeId 100 in schema 68 (e.g. "Store Technology")
    • customfield_10225 System — Assets CMDB, objectTypeId 103 in schema 68 (e.g. "UKG Pro WFM")
    • customfield_10233 Cause — Assets CMDB, objectTypeId 107 "Cause Code" in schema 68 (e.g. "Unknown")

Verified via real closed tickets (SS-20272, SS-20022, SS-19863). Notably, all three sample tickets used Cause: "Unknown" (objectId 83132) — meaning the workflow accepts "Unknown" as a legitimate value. AI agent never needs to guess a root cause; a safe fallback exists.

There is no dedicated Cancel transition. Cancel is expressed via resolution: Won't Do on the Resolved transition. Duplicate is resolution: Duplicate + an issue link.

Design

Cache infrastructure (all 3 new CMDB fields live in schema 68, same permission profile as Stores):

  • Refactor services/jira/storesCache.js into a services/jira/assetsObjectCache.js factory. Same in-memory Map + on-disk JSON + startAt/maxResults pagination + PAT auth. Store cache becomes one instance; three new caches for Business Service / System / Cause.
  • All 4 caches init on boot; GET /api/wxccai/admin/storesCache/status -> GET /admin/cachesStatus returning all four; POST /admin/cachesRefresh?name=businessServices|... for on-demand refresh.

Config-driven defaults in src/config/ssCloseDefaults.js:

export const SS_CLOSE_DEFAULTS = {
  '__default__':                            { component: '…', businessService: 'Store Technology', system: '…', cause: 'Unknown' },
  'UKG Pro / Workforce Management Issues':  { component: 'UKG_COE', businessService: 'Store Technology', system: 'UKG Pro WFM', cause: 'Unknown' },
  // … seeded from real closed-ticket patterns per subType
};

Enhanced close endpoint (backward-compatible with existing /close):

POST /api/wxccai/ticket/:key/close
Body: {
  resolution:      'Done' | 'Won\'t Do' | 'Duplicate' | 'Fixed' | 'Cannot Reproduce' | 'Invalid' | ...,
  comment:         string?,
  component:       string?  (defaults from subType map),
  businessService: string?  (defaults from subType map),
  system:          string?  (defaults from subType map),
  cause:           string?  (defaults 'Unknown'),
  internal:        bool?
}

Under the hood:

  1. Resolve name -> objectId via caches (Business Service / System / Cause)
  2. PUT /issue/{key} to set the 4 fields (using Cloud CMDB shape [{id: <ws>:<obj>}])
  3. POST /issue/{key}/transitions with { transition: { id: '161' }, fields: { resolution: { name: '…' } } }
  4. POST /issue/{key}/comment for the audit trail
  5. Also fixes #9 (only send resolution when target statusCategory is done).

Convenience wrappers for common CC-agent intents:

POST /ticket/:key/confirmFixed          -> close with resolution=Done, comment='Customer confirmed fixed via CC agent'
POST /ticket/:key/customerCancelled     -> close with resolution=Won't Do
POST /ticket/:key/duplicate  body: { primaryKey: 'SS-XXXXX' }   -> close with resolution=Duplicate + create formal issueLink type 'Duplicate'

What the AI needs from the caller

CC intent Required from caller
"It's fixed" Just the ticket key; everything else defaults
"Cancel my ticket" Ticket key + optional reason for the comment
"Duplicate of SS-XXXX" Both ticket keys
Ambiguous Escalate to human; don't close

Acceptance

  • Boot the app with the 4 caches populated (< 15s total).
  • POST /ticket/SS-20948/confirmFixed transitions to Resolved with all 4 validator fields populated automatically from defaults + resolution=Done. Verified via direct REST GET.
  • POST /ticket/:key/duplicate also creates a formal Duplicate issueLink to the primary.
  • Existing /close endpoint continues to work with its old body shape.
  • Fixes #9 en passant.

Non-goals

  • Handling every workflow permutation (agent-assigned tickets, tickets already in Work in progress). Those transitions differ slightly; extension is straightforward once the base close works.
  • Learning from history to auto-refine defaults. Just seed the config file from the most common combos I observed and let ops iterate.
  • Backfilling any Component/Business Service/System/Cause data on old tickets.
Extend the app so the Contact Center AI agent can programmatically close SS tickets end-to-end — the natural next step after ticket creation lands. Design informed by direct Jira workflow probing. ### Workflow reality check The `Resolved` transition (id 161) in project SS has: - **1 field on the transition screen**: `resolution` (8 valid values: `Done`, `Won't Do`, `Duplicate`, `Fixed`, `Cannot Reproduce`, `Blocked`, `Invalid`, `Declined`) - **4 fields checked by a workflow validator** (NOT on the transition screen — must already be set on the ticket before you call the transition): - `components` — Jira native, 63 fixed options - `customfield_10224` **Business Service** — Assets CMDB, objectTypeId **100** in schema 68 (e.g. "Store Technology") - `customfield_10225` **System** — Assets CMDB, objectTypeId **103** in schema 68 (e.g. "UKG Pro WFM") - `customfield_10233` **Cause** — Assets CMDB, objectTypeId **107** "Cause Code" in schema 68 (e.g. "Unknown") Verified via real closed tickets (SS-20272, SS-20022, SS-19863). Notably, all three sample tickets used `Cause: "Unknown"` (objectId 83132) — meaning **the workflow accepts "Unknown" as a legitimate value**. AI agent never needs to guess a root cause; a safe fallback exists. There is **no dedicated Cancel transition**. Cancel is expressed via `resolution: Won't Do` on the Resolved transition. Duplicate is `resolution: Duplicate` + an issue link. ### Design **Cache infrastructure** (all 3 new CMDB fields live in schema 68, same permission profile as Stores): - Refactor `services/jira/storesCache.js` into a `services/jira/assetsObjectCache.js` factory. Same in-memory Map + on-disk JSON + startAt/maxResults pagination + PAT auth. Store cache becomes one instance; three new caches for Business Service / System / Cause. - All 4 caches init on boot; `GET /api/wxccai/admin/storesCache/status` -> `GET /admin/cachesStatus` returning all four; `POST /admin/cachesRefresh?name=businessServices|...` for on-demand refresh. **Config-driven defaults** in `src/config/ssCloseDefaults.js`: ```js export const SS_CLOSE_DEFAULTS = { '__default__': { component: '…', businessService: 'Store Technology', system: '…', cause: 'Unknown' }, 'UKG Pro / Workforce Management Issues': { component: 'UKG_COE', businessService: 'Store Technology', system: 'UKG Pro WFM', cause: 'Unknown' }, // … seeded from real closed-ticket patterns per subType }; ``` **Enhanced close endpoint** (backward-compatible with existing `/close`): ``` POST /api/wxccai/ticket/:key/close Body: { resolution: 'Done' | 'Won\'t Do' | 'Duplicate' | 'Fixed' | 'Cannot Reproduce' | 'Invalid' | ..., comment: string?, component: string? (defaults from subType map), businessService: string? (defaults from subType map), system: string? (defaults from subType map), cause: string? (defaults 'Unknown'), internal: bool? } ``` Under the hood: 1. Resolve name -> objectId via caches (Business Service / System / Cause) 2. `PUT /issue/{key}` to set the 4 fields (using Cloud CMDB shape `[{id: <ws>:<obj>}]`) 3. `POST /issue/{key}/transitions` with `{ transition: { id: '161' }, fields: { resolution: { name: '…' } } }` 4. `POST /issue/{key}/comment` for the audit trail 5. Also **fixes #9** (only send `resolution` when target statusCategory is `done`). **Convenience wrappers** for common CC-agent intents: ``` POST /ticket/:key/confirmFixed -> close with resolution=Done, comment='Customer confirmed fixed via CC agent' POST /ticket/:key/customerCancelled -> close with resolution=Won't Do POST /ticket/:key/duplicate body: { primaryKey: 'SS-XXXXX' } -> close with resolution=Duplicate + create formal issueLink type 'Duplicate' ``` ### What the AI needs from the caller | CC intent | Required from caller | | --------- | -------------------- | | "It's fixed" | Just the ticket key; everything else defaults | | "Cancel my ticket" | Ticket key + optional reason for the comment | | "Duplicate of SS-XXXX" | Both ticket keys | | Ambiguous | Escalate to human; don't close | ### Acceptance - Boot the app with the 4 caches populated (< 15s total). - `POST /ticket/SS-20948/confirmFixed` transitions to `Resolved` with all 4 validator fields populated automatically from defaults + `resolution=Done`. Verified via direct REST GET. - `POST /ticket/:key/duplicate` also creates a formal `Duplicate` issueLink to the primary. - Existing `/close` endpoint continues to work with its old body shape. - Fixes #9 en passant. ### Non-goals - Handling every workflow permutation (agent-assigned tickets, tickets already in Work in progress). Those transitions differ slightly; extension is straightforward once the base close works. - Learning from history to auto-refine defaults. Just seed the config file from the most common combos I observed and let ops iterate. - Backfilling any Component/Business Service/System/Cause data on old tickets.
jmcqueen added this to the v1: Jira lifecycle GA milestone 2026-07-07 10:52:17 -04:00
jmcqueen added the
enhancement
label 2026-07-07 10:52:17 -04:00
Author
Owner

Landed on branch cursor/close-tickets as commit 034e657.

What shipped

  • Generalized storesCache into a reusable assetsObjectCache factory. Store cache is now one instance; three new caches join it for the CMDB validator fields on close (Business Services / Systems / Cause Codes).
  • All 4 caches boot in parallel; ~5s cold-start in the tenant (2661 stores dominate).
  • Admin surface rebuilt around /admin/caches (plural). Old /admin/storesCache/* kept as aliases.
  • Config renamed to caches.* / CACHES_* env vars.
  • src/config/ssCloseDefaults.js seeds every supported SS subType with a {component, businessService, system, cause} tuple. All values verified against live caches. Falls back to the tenant's designed catch-all values (Help Desk / Store Technology / I can't find my option - Misc / Unknown) for unmapped subTypes.
  • issues.js closeTicket reworked:
    • setSSValidatorFields helper reads current ticket state, resolves each field via caller > existing-on-ticket > default, PUTs the four fields using the Cloud CMDB shape [{id: "<ws>:<objectId>"}].
    • Preserves human triage by default (preserveExistingClassification=true).
    • Fixes #9 en passant (only sends resolution on done-category transitions).
  • closeHelpers.js adds three CC-agent intent wrappers: confirmFixed, customerCancelled, markDuplicate (last one creates a formal Duplicate issueLink to the primary).
  • New routes: POST /ticket/:key/{confirmFixed,customerCancelled,duplicate} + expanded body on the existing /close route.

Verified end-to-end

$ curl -X POST http://localhost:1866/api/wxccai/ticket/SS-20948/confirmFixed \
    -H 'Content-Type: application/json' \
    -d '{"subType":"Report a Technology issue","comment":"[test] CC-agent confirmFixed pathway"}'
{
  "success": true,
  "key": "SS-20948",
  "transitionId": "161",
  "resolution": "Done",
  "transitionUsed": "Resolved",
  "newStatusCategory": "done",
  "validatorFields": {
    "applied": {
      "components": "Help Desk (default for 'Report a Technology issue')",
      "customfield_10224": "Store Technology (default, objectId 83120)",
      "customfield_10225": "I can't find my option - Misc (default, objectId 83158)",
      "customfield_10233": "Unknown (default, objectId 83132)"
    }
  }
}

Direct Jira REST GET confirms every field persisted on the ticket. Status now Resolved (category done) with resolution Done.

CC-agent surface at rest

Intent Endpoint Minimum body
"Fixed" POST /ticket/:key/confirmFixed { subType }
"Cancel my ticket" POST /ticket/:key/customerCancelled { subType, reason? }
"Duplicate of SS-nnnn" POST /ticket/:key/duplicate { primaryKey }
Full control POST /ticket/:key/close see README
Landed on branch `cursor/close-tickets` as commit [`034e657`](https://git.joesjavajoint.com/jmcqueen/wxccai/commit/034e657). ### What shipped - Generalized `storesCache` into a reusable `assetsObjectCache` factory. Store cache is now one instance; three new caches join it for the CMDB validator fields on close (Business Services / Systems / Cause Codes). - All 4 caches boot in parallel; ~5s cold-start in the tenant (2661 stores dominate). - Admin surface rebuilt around `/admin/caches` (plural). Old `/admin/storesCache/*` kept as aliases. - Config renamed to `caches.*` / `CACHES_*` env vars. - `src/config/ssCloseDefaults.js` seeds every supported SS subType with a `{component, businessService, system, cause}` tuple. All values verified against live caches. Falls back to the tenant's designed catch-all values (`Help Desk` / `Store Technology` / `I can't find my option - Misc` / `Unknown`) for unmapped subTypes. - `issues.js closeTicket` reworked: - `setSSValidatorFields` helper reads current ticket state, resolves each field via caller > existing-on-ticket > default, PUTs the four fields using the Cloud CMDB shape `[{id: "<ws>:<objectId>"}]`. - Preserves human triage by default (`preserveExistingClassification=true`). - Fixes #9 en passant (only sends `resolution` on done-category transitions). - `closeHelpers.js` adds three CC-agent intent wrappers: `confirmFixed`, `customerCancelled`, `markDuplicate` (last one creates a formal `Duplicate` issueLink to the primary). - New routes: `POST /ticket/:key/{confirmFixed,customerCancelled,duplicate}` + expanded body on the existing `/close` route. ### Verified end-to-end ``` $ curl -X POST http://localhost:1866/api/wxccai/ticket/SS-20948/confirmFixed \ -H 'Content-Type: application/json' \ -d '{"subType":"Report a Technology issue","comment":"[test] CC-agent confirmFixed pathway"}' { "success": true, "key": "SS-20948", "transitionId": "161", "resolution": "Done", "transitionUsed": "Resolved", "newStatusCategory": "done", "validatorFields": { "applied": { "components": "Help Desk (default for 'Report a Technology issue')", "customfield_10224": "Store Technology (default, objectId 83120)", "customfield_10225": "I can't find my option - Misc (default, objectId 83158)", "customfield_10233": "Unknown (default, objectId 83132)" } } } ``` Direct Jira REST GET confirms every field persisted on the ticket. Status now `Resolved` (category `done`) with resolution `Done`. ### CC-agent surface at rest | Intent | Endpoint | Minimum body | | --- | --- | --- | | "Fixed" | `POST /ticket/:key/confirmFixed` | `{ subType }` | | "Cancel my ticket" | `POST /ticket/:key/customerCancelled` | `{ subType, reason? }` | | "Duplicate of SS-nnnn" | `POST /ticket/:key/duplicate` | `{ primaryKey }` | | Full control | `POST /ticket/:key/close` | see README |
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: jmcqueen/wxccai#10
No description provided.