{
  "repo": "tidbcloud/jiraport",
  "binary": "resolver",
  "binary_path": "cmd/resolver/main.go",
  "dependencies": {
    "Runtime JiraportResolverLifecycle.Startup": {
      "infrastructure": {},
      "services": []
    },
    "Runtime JiraportResolverLifecycle.LoadConfig": {
      "infrastructure": {
        "filesystem": {
          "conditions": [
            "Always"
          ],
          "operations": [
            {
              "operation": "READ FILE",
              "trigger": "Load config.yaml from the configured CONFIG_PATH directory before any DB or handler initialization",
              "conditions": [
                "Always"
              ],
              "implementation": "LoadConfig(configPath) -> viper.ReadInConfig()"
            }
          ]
        }
      },
      "services": []
    },
    "Runtime JiraportResolverLifecycle.InitializeDatastores": {
      "infrastructure": {
        "mysql_policy": {
          "conditions": [
            "Always"
          ],
          "operations": [
            {
              "operation": "OPEN DB HANDLE",
              "trigger": "Create the read-mostly policy DB connection used by engineer lookups and region contact queries",
              "conditions": [
                "Always"
              ],
              "implementation": "gorm.Open(mysql.Open(viper.GetString(\"policy-db-dsn\")), ...)"
            }
          ]
        },
        "mysql_data": {
          "conditions": [
            "Always"
          ],
          "operations": [
            {
              "operation": "OPEN DB HANDLE",
              "trigger": "Create the data DB connection used for issues, notifications, messages, actions, and assignment history",
              "conditions": [
                "Always"
              ],
              "implementation": "gorm.Open(mysql.Open(viper.GetString(\"data-db-dsn\")), ...)"
            },
            {
              "operation": "AUTO MIGRATE",
              "trigger": "Ensure the issue/notification/message/action/assign tables exist before serving events",
              "conditions": [
                "Always"
              ],
              "implementation": "db.New(dataDB) -> dataDB.AutoMigrate(&entity.Issue{}, &entity.Notification{}, &entity.NotificationActionExecInfo{}, &entity.Message{}, &entity.AssignHistory{})"
            }
          ]
        },
        "mysql_info": {
          "conditions": [
            "Always"
          ],
          "operations": [
            {
              "operation": "OPEN DB HANDLE",
              "trigger": "Create the info DB connection used by clinic helpers for tenant and region lookups",
              "conditions": [
                "Always"
              ],
              "implementation": "gorm.Open(mysql.Open(viper.GetString(\"info-db-dsn\")), ...)"
            }
          ]
        },
        "filesystem": {
          "conditions": [
            "Always"
          ],
          "operations": [
            {
              "operation": "READ FILE",
              "trigger": "Load notification policy definitions used later by the notification state machine",
              "conditions": [
                "Always"
              ],
              "implementation": "db.New(dataDB) -> os.ReadFile(viper.GetString(\"policy-file\")); yaml.Unmarshal(...)"
            }
          ]
        }
      },
      "services": []
    },
    "Runtime JiraportResolverLifecycle.InitializeClients": {
      "infrastructure": {
        "aws_sdk_config": {
          "conditions": [
            "Only when resolver-url is empty and is-local-dev is false"
          ],
          "operations": [
            {
              "operation": "LOAD AWS CONFIG",
              "trigger": "Create the AWS Lambda client used by notification transitions to enqueue NotificationAction events",
              "conditions": [
                "viper.GetString(\"resolver-url\") == \"\"",
                "viper.GetBool(\"is-local-dev\") == false"
              ],
              "implementation": "lambdacli.New(logger, resolver-function-name) -> awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(\"us-west-1\"))"
            }
          ]
        }
      },
      "services": []
    },
    "Runtime JiraportResolverLifecycle.RegisterHandlers": {
      "infrastructure": {
        "filesystem": {
          "conditions": [
            "Always"
          ],
          "operations": [
            {
              "operation": "READ FILE",
              "trigger": "Load policy and event matcher YAML files for JiraIssue event classification",
              "conditions": [
                "Always"
              ],
              "implementation": "jiraHandler.New -> matcher.NewMatcher(policy-matcher-file); matcher.NewMatcher(event-matcher-file)"
            },
            {
              "operation": "READ FILE",
              "trigger": "Load assign and fbowner matcher YAML files when constructing NotificationAction commands",
              "conditions": [
                "Always for fbowner matcher; only when need-assign is true for assign matcher"
              ],
              "implementation": "NewAssignCommand -> matcher.NewMatcher(assign-matcher-file); NewFillFBOwnerCommand -> matcher.NewMatcher(fbowner-matcher-file)"
            },
            {
              "operation": "READ FILE",
              "trigger": "Load the Lark card template file used by notify and fillfbowner commands",
              "conditions": [
                "Always"
              ],
              "implementation": "render.New(...) -> os.ReadFile(viper.GetString(\"template-file\")); template.Parse(...)"
            }
          ]
        }
      },
      "services": []
    },
    "Runtime JiraportResolverLifecycle.StartRuntime": {
      "infrastructure": {},
      "services": []
    },
    "HTTP GET /healthz": {
      "infrastructure": {},
      "services": []
    },
    "HTTP POST /": {
      "infrastructure": {},
      "services": []
    },
    "Runtime JiraportResolverEvents.HandleJiraIssue": {
      "infrastructure": {
        "mysql_policy": {
          "conditions": [
            "Only when the Jira payload has an assignee with AccountID and the prefill step runs"
          ],
          "operations": [
            {
              "operation": "SELECT",
              "trigger": "Resolve Jira assignee account ID to email before policy matching and issue persistence",
              "conditions": [
                "jiraEvent.Issue.Fields.Assignee != nil",
                "jiraEvent.Issue.Fields.Assignee.AccountID != \"\""
              ],
              "implementation": "PrefillEvent -> engClient.GetEmailByAccountID(accountID) -> policyDB.Raw(...).Scan(&email)"
            }
          ]
        },
        "mysql_data": {
          "conditions": [
            "Always after the Jira payload decodes successfully"
          ],
          "operations": [
            {
              "operation": "UPSERT ISSUE",
              "trigger": "Persist the normalized issue if this issue key has not been seen before",
              "conditions": [
                "jiraEvent.ToIssue() succeeds"
              ],
              "implementation": "dbClient.CreateIssue(issue) -> SELECT issue key; INSERT ... ON CONFLICT DO NOTHING"
            },
            {
              "operation": "INSERT NOTIFICATION",
              "trigger": "Create one notification row per matched policy before running transitions",
              "conditions": [
                "matchPolicy(...) returned at least one policy"
              ],
              "implementation": "dbClient.CreateNotification(policy, issue) -> INSERT notification with state Inited"
            },
            {
              "operation": "LOCK + UPDATE + INSERT ACTION",
              "trigger": "Advance all notifications for the issue under the matched event and materialize pending action rows",
              "conditions": [
                "matchEvent(...) returned a transition event",
                "the issue has existing notification rows"
              ],
              "implementation": "notificationSvc.GlobalTransitionWithUpdate(issue, event) -> dbClient.GlobalTransitionWithUpdate(...)"
            }
          ]
        }
      },
      "services": [
        {
          "name": "jiraport-resolver-action-queue",
          "target_interface": "NotificationAction async event",
          "operation_type": "async_event",
          "target_repo": "tidbcloud/jiraport",
          "conditions": [
            "GlobalTransitionWithUpdate produced at least one NotificationActionExecInfo row"
          ],
          "operations": [
            {
              "operation": "HTTP POST OR LAMBDA INVOKE",
              "trigger": "Enqueue follow-up NotificationAction events after notification transitions",
              "conditions": [
                "At least one actionExecInfo is returned by dbClient.GlobalTransitionWithUpdate"
              ],
              "implementation": "Notifications.doAction -> lambdaClient.InvokeAsync(context.Background(), Event{Name:\"NotificationAction\"})"
            }
          ]
        }
      ]
    },
    "Runtime JiraportResolverEvents.HandleNotificationAck": {
      "infrastructure": {
        "mysql_data": {
          "conditions": [
            "Always after the ack payload decodes successfully"
          ],
          "operations": [
            {
              "operation": "SELECT MESSAGE",
              "trigger": "Resolve the latest stored notification message by Lark message_id",
              "conditions": [
                "Always"
              ],
              "implementation": "dbClient.GetMessage(&msgIndex) -> SELECT ... FROM messages ORDER BY id DESC LIMIT 1"
            },
            {
              "operation": "SELECT NOTIFICATION",
              "trigger": "Load the linked notification and joined issue before applying the acknowledge transition",
              "conditions": [
                "GetMessage(...) succeeded"
              ],
              "implementation": "dbClient.GetNotification(msg.NotificationID) -> JOINS Issue"
            },
            {
              "operation": "LOCK + UPDATE + INSERT ACTION",
              "trigger": "Apply AcknowledgeEvent to the notification state machine and create any follow-up action row",
              "conditions": [
                "GetNotification(...) succeeded"
              ],
              "implementation": "notificationSvc.Transition(msg.NotificationID, config.AcknowledgeEvent) -> dbClient.Transition(...)"
            }
          ]
        }
      },
      "services": [
        {
          "name": "jira-rest-api",
          "target_interface": "POST issue comment",
          "operation_type": "http_post",
          "target_repo": "atlassian/jira",
          "conditions": [
            "dbClient.GetNotification(...) succeeded"
          ],
          "operations": [
            {
              "operation": "ADD COMMENT",
              "trigger": "Append the Lark response marker to the Jira issue after the ack transition runs",
              "conditions": [
                "notification lookup succeeded"
              ],
              "implementation": "jiraClient.AddComment(notification.IssueKey, ...)"
            }
          ]
        },
        {
          "name": "jiraport-resolver-action-queue",
          "target_interface": "NotificationAction async event",
          "operation_type": "async_event",
          "target_repo": "tidbcloud/jiraport",
          "conditions": [
            "dbClient.Transition(...) returned a pending action"
          ],
          "operations": [
            {
              "operation": "HTTP POST OR LAMBDA INVOKE",
              "trigger": "Enqueue follow-up NotificationAction after the ack transition if the state machine emits one",
              "conditions": [
                "notification transition produced actionExecInfo"
              ],
              "implementation": "Notifications.doAction -> lambdaClient.InvokeAsync(context.Background(), Event{Name:\"NotificationAction\"})"
            }
          ]
        }
      ]
    },
    "Runtime JiraportResolverEvents.HandleNotificationAction": {
      "infrastructure": {
        "mysql_data": {
          "conditions": [
            "Always after the action payload decodes successfully"
          ],
          "operations": [
            {
              "operation": "INSERT MESSAGE",
              "trigger": "Record successful Lark notification message IDs so later ack callbacks can be correlated",
              "conditions": [
                "actionExecInfo.Action.Cmd[0] == \"notify\"",
                "a Lark send succeeded"
              ],
              "implementation": "NotifyCommand.Exec -> dbClient.AddMessage(notification.ID, msgIndex)"
            },
            {
              "operation": "SELECT ASSIGN HISTORY + UPSERT ASSIGN HISTORY",
              "trigger": "Choose a fair assignee for assign/custom commands",
              "conditions": [
                "actionExecInfo.Action.Cmd[0] == \"assign\" or actionExecInfo.Action.Cmd[0] == \"custom\""
              ],
              "implementation": "dbClient.SelectAssigneeFromCandidate(emails)"
            },
            {
              "operation": "SELECT ACTION EXEC INFO",
              "trigger": "Poll the current notification state until timeout expires or the state changes",
              "conditions": [
                "actionExecInfo.Action.Cmd[0] == \"timeout\""
              ],
              "implementation": "TimeoutCommand.Exec -> dbClient.GetNotificationActionExecInfo(actionExecInfo.ID)"
            },
            {
              "operation": "UPDATE ACTION STATUS",
              "trigger": "Mark the action row finished as true, false, or error once execution completes",
              "conditions": [
                "executeAction(...) returned and cmdErr != errContextDone"
              ],
              "implementation": "dbClient.SetNotificationActionExecInfoFinished(actionExecInfo.ID, status)"
            },
            {
              "operation": "LOCK + UPDATE + INSERT ACTION",
              "trigger": "Advance the notification state machine according to OnSuccess/OnFailure/OnError after the command finishes",
              "conditions": [
                "executeAction(...) returned and cmdErr != errContextDone"
              ],
              "implementation": "notificationSvc.Transition(actionExecInfo.Notification.ID, transitionEvent) -> dbClient.Transition(...)"
            }
          ]
        },
        "mysql_policy": {
          "conditions": [
            "Only for commands or receiver selectors that consult engineer or region policy data"
          ],
          "operations": [
            {
              "operation": "SELECT ENGINEERS / CONTACTS / SHIFTS",
              "trigger": "Resolve assignees, firemasters, secondary contacts, leaders, custom assignees, TSEs, SEE candidates, and region contacts",
              "conditions": [
                "actionExecInfo.Action.Cmd[0] is one of assign, fillfbowner, escalate, custom, or notify with receiverType in {assignee, firemaster, secondary, leader, custom, regioncc}"
              ],
              "implementation": "engineerClient.GetEngineerByEmail/GetFiremasters/GetSecondaryByEmail/GetEscalationChainEngineerByAssigneeEmail/GetEngineersByCategoryIDs/GetTSEByTenantID/GetSEEs/GetComponentsByUserEmail and clinicClient.GetRegionContactByRegion"
            }
          ]
        }
      },
      "services": [
        {
          "name": "jira-rest-api",
          "target_interface": "issue comment / assign / transition / custom field update endpoints",
          "operation_type": "http",
          "target_repo": "atlassian/jira",
          "conditions": [
            "actionExecInfo.Action.Cmd[0] is one of assign, fillfbowner, escalate, comment, backfill, custom, transition",
            "or actionExecInfo.Action.LogOnComment == true and cmdMsg != \"\""
          ],
          "operations": [
            {
              "operation": "ADD COMMENT",
              "trigger": "Execute comment command or append command output when LogOnComment is enabled",
              "conditions": [
                "cmd[0] == \"comment\"",
                "or actionExecInfo.Action.LogOnComment == true and cmdMsg != \"\""
              ],
              "implementation": "CommentCommand.Exec / NotificationActionHandler.Handle -> jiraClient.AddComment(issueKey, body)"
            },
            {
              "operation": "ASSIGN ISSUE",
              "trigger": "Assign the issue to an assignee or escalation target",
              "conditions": [
                "cmd[0] == \"assign\" or cmd[0] == \"escalate\""
              ],
              "implementation": "AssignCommand.assign/assignTSE or EscalateCommand.escalate -> jiraClient.Assign(issue.Key, engineer.Email)"
            },
            {
              "operation": "ASSIGN FB OWNER",
              "trigger": "Fill the FB owner custom field using the matched FB owner candidate",
              "conditions": [
                "cmd[0] == \"fillfbowner\""
              ],
              "implementation": "FillFBOwnerCommand.assignFBOwner -> jiraClient.AssignFBOwner(issue.Key, assignee.Email)"
            },
            {
              "operation": "UPDATE CUSTOM FIELD",
              "trigger": "Backfill tenant/version/cluster fields or update a custom SEE field",
              "conditions": [
                "cmd[0] == \"backfill\" or cmd[0] == \"custom\""
              ],
              "implementation": "jiraClient.BackfillTenantID/BackfillVersion/BackfillClusterIDForTicket/UpdateCustomSEE2"
            },
            {
              "operation": "TRANSITION ISSUE",
              "trigger": "Move the Jira issue to the requested transition ID",
              "conditions": [
                "cmd[0] == \"transition\""
              ],
              "implementation": "TransitionCommand.Exec -> jiraClient.Transition(issueKey, transitionID)"
            }
          ]
        },
        {
          "name": "lark-notify-service",
          "target_interface": "Notify(card|urgent|sms|phone)",
          "operation_type": "http_post",
          "target_repo": "tidbcloud/notify-service",
          "conditions": [
            "cmd[0] == \"notify\"",
            "or cmd[0] == \"fillfbowner\" and a template argument is provided"
          ],
          "operations": [
            {
              "operation": "SEND MESSAGE",
              "trigger": "Send rendered notification cards or urgent messages to selected receivers",
              "conditions": [
                "cmd[0] == \"notify\""
              ],
              "implementation": "NotifyCommand.Exec -> larkClient.Notify(msg)"
            },
            {
              "operation": "SEND MESSAGE",
              "trigger": "Optionally notify the newly assigned FB owner with a rendered template card",
              "conditions": [
                "cmd[0] == \"fillfbowner\"",
                "len(args) > 0"
              ],
              "implementation": "FillFBOwnerCommand.Exec -> larkClient.Notify(message)"
            }
          ]
        },
        {
          "name": "clinic-api",
          "target_interface": "cluster/org/report HTTP APIs",
          "operation_type": "http",
          "target_repo": "tidbcloud/clinic",
          "conditions": [
            "cmd[0] is one of notify, fillfbowner, report, backfill",
            "or receiverType/template functions require clinic lookups"
          ],
          "operations": [
            {
              "operation": "GET REGION / ALIAS / CLUSTER NAME",
              "trigger": "Resolve region contacts or render notification templates with tenant/cluster context",
              "conditions": [
                "cmd[0] == \"notify\" or cmd[0] == \"fillfbowner\"",
                "the chosen receiver selector or template function needs clinic data"
              ],
              "implementation": "RegionCCReceiverSelector.SelectReceiver and render template funcs -> clinicClient.GetRegionByTenantIDAndClusterID/GetAliasByTenantIDAndClusterID/GetClusterName"
            },
            {
              "operation": "GET TENANT / ORG / GENERATE REPORT",
              "trigger": "Generate a clinic workload report address for the issue",
              "conditions": [
                "cmd[0] == \"report\""
              ],
              "implementation": "ReportCommand.Exec -> clinicClient.GetTenantIDByTenantIDAndClusterID/GetClinicOrgID/GenerateReport"
            },
            {
              "operation": "GET TENANT / CLUSTER VERSION",
              "trigger": "Backfill tenant ID or cluster version for O11Y tickets",
              "conditions": [
                "cmd[0] == \"backfill\"",
                "issue.Key starts with \"O11Y\""
              ],
              "implementation": "BackfillCommand.Exec -> clinicClient.GetTenantIDByTenantIDAndClusterID/GetClusterVersion"
            }
          ]
        },
        {
          "name": "devops-api",
          "target_interface": "guardian analysis and ST cluster ID endpoints",
          "operation_type": "http",
          "target_repo": "tidbcloud/devops",
          "conditions": [
            "cmd[0] == \"guardian\"",
            "or cmd[0] == \"backfill\" and issue.Key starts with \"ST-\""
          ],
          "operations": [
            {
              "operation": "CREATE ANALYSIS",
              "trigger": "Start a Guardian analysis for the Jira issue",
              "conditions": [
                "cmd[0] == \"guardian\""
              ],
              "implementation": "GuardianCommand.Exec -> devopsClient.CreateAnalysis(issueKey)"
            },
            {
              "operation": "GET CLUSTER ID",
              "trigger": "Backfill cluster ID for ST tickets that do not already have cluster asset metadata",
              "conditions": [
                "cmd[0] == \"backfill\"",
                "issue.Key starts with \"ST-\"",
                "customfield_10111 and customfield_10088 are both empty"
              ],
              "implementation": "BackfillCommand.Exec -> devopsClient.GetClusterIDForStTicket(issue.Key)"
            }
          ]
        },
        {
          "name": "arbitrary-http-endpoint",
          "target_interface": "custom address",
          "operation_type": "http",
          "target_repo": "unknown/external-http",
          "conditions": [
            "cmd[0] == \"http_forward\"",
            "len(args) >= 2"
          ],
          "operations": [
            {
              "operation": "HTTP REQUEST",
              "trigger": "Forward the issue payload to an arbitrary external HTTP endpoint",
              "conditions": [
                "cmd[0] == \"http_forward\""
              ],
              "implementation": "HTTPForwardCommand.Exec -> http.NewRequestWithContext(method, address, body); client.Do(req)"
            }
          ]
        },
        {
          "name": "jiraport-resolver-action-queue",
          "target_interface": "NotificationAction async event",
          "operation_type": "async_event",
          "target_repo": "tidbcloud/jiraport",
          "conditions": [
            "notificationSvc.Transition(...) returned a pending follow-up action"
          ],
          "operations": [
            {
              "operation": "HTTP POST OR LAMBDA INVOKE",
              "trigger": "Enqueue the next NotificationAction after the current action finishes and the state machine advances",
              "conditions": [
                "dbClient.Transition(...) returned actionExecInfo"
              ],
              "implementation": "Notifications.doAction -> lambdaClient.InvokeAsync(context.Background(), Event{Name:\"NotificationAction\"})"
            }
          ]
        }
      ]
    },
    "Runtime JiraportResolverEvents.HandleHookEvent": {
      "infrastructure": {},
      "services": [
        {
          "name": "jira-rest-api",
          "target_interface": "POST issue comment",
          "operation_type": "http_post",
          "target_repo": "atlassian/jira",
          "conditions": [
            "The hook payload decodes successfully"
          ],
          "operations": [
            {
              "operation": "ADD COMMENT",
              "trigger": "Append the provided markdown content to the target Jira issue",
              "conditions": [
                "hookEvent.JiraIssueID != \"\""
              ],
              "implementation": "jiraClient.AddComment(hookEvent.JiraIssueID, hookEvent.MarkdownContent)"
            }
          ]
        }
      ]
    }
  },
  "notes": [
    "POST / in server-mode is only a transport shim for lambda.Event JSON; the actual external dependencies live in the four event handlers selected by event.Name.",
    "NotificationAction has the widest fan-out. Its downstream dependencies are determined first by cmd[0], then by template content, receiverType, issue prefix, and transition outcomes."
  ]
}
