{
  "repo": "tidbcloud/jiralert",
  "binary": "jiralert",
  "binary_path": "cmd/jiralert/main.go",
  "dependencies": {
    "Runtime JiralertLifecycle.Startup": {
      "infrastructure": {},
      "services": []
    },
    "Runtime JiralertLifecycle.LoadConfig": {
      "infrastructure": {
        "filesystem": {
          "conditions": [
            "Only when CONFIG_PATH or -config resolves to a non-empty path"
          ],
          "operations": [
            {
              "operation": "READ FILE",
              "trigger": "Load the YAML configuration from disk before any service clients are initialized",
              "conditions": [
                "configPath != \"\""
              ],
              "implementation": "config.LoadFile(configPath, logger) -> os.ReadFile(filename)"
            }
          ]
        },
        "aws_secrets_manager": {
          "conditions": [
            "Only when configPath is empty and CONFIG_SECRET_NAME is set"
          ],
          "operations": [
            {
              "operation": "GET SECRET",
              "trigger": "Fetch the configuration payload from AWS Secrets Manager instead of a local file",
              "conditions": [
                "configPath == \"\"",
                "configSecretName != \"\""
              ],
              "implementation": "retriveSecretManager(configSecretName, logger) -> secretsmanager.GetSecretValue(ctx, input)"
            }
          ]
        }
      },
      "services": []
    },
    "Runtime JiralertLifecycle.LoadTemplate": {
      "infrastructure": {
        "filesystem": {
          "conditions": [
            "Only when conf.TemplateSecretName is empty"
          ],
          "operations": [
            {
              "operation": "READ FILE",
              "trigger": "Load the text/template definitions from the configured template file",
              "conditions": [
                "conf.TemplateSecretName == \"\""
              ],
              "implementation": "template.LoadTemplate(conf.Template, logger)"
            }
          ]
        },
        "aws_secrets_manager": {
          "conditions": [
            "Only when conf.TemplateSecretName is set"
          ],
          "operations": [
            {
              "operation": "GET SECRET",
              "trigger": "Fetch the template body from AWS Secrets Manager",
              "conditions": [
                "conf.TemplateSecretName != \"\""
              ],
              "implementation": "retriveSecretManager(conf.TemplateSecretName, logger) -> secretsmanager.GetSecretValue(ctx, input)"
            }
          ]
        }
      },
      "services": []
    },
    "Runtime JiralertLifecycle.LoadPrecheckMatcher": {
      "infrastructure": {
        "filesystem": {
          "conditions": [
            "Only when conf.PrecheckSecretName is empty"
          ],
          "operations": [
            {
              "operation": "READ FILE",
              "trigger": "Load the precheck matcher YAML from the configured path",
              "conditions": [
                "conf.PrecheckSecretName == \"\"",
                "conf.PrecheckPath != \"\""
              ],
              "implementation": "matcher.LoadFile(conf.PrecheckPath, logger) -> os.ReadFile(filepath)"
            }
          ]
        },
        "aws_secrets_manager": {
          "conditions": [
            "Only when conf.PrecheckSecretName is set"
          ],
          "operations": [
            {
              "operation": "GET SECRET",
              "trigger": "Fetch the precheck matcher definition from AWS Secrets Manager",
              "conditions": [
                "conf.PrecheckSecretName != \"\""
              ],
              "implementation": "retriveSecretManager(conf.PrecheckSecretName, logger) -> secretsmanager.GetSecretValue(ctx, input)"
            }
          ]
        }
      },
      "services": []
    },
    "Runtime JiralertLifecycle.InitializeLocks": {
      "infrastructure": {},
      "services": [
        {
          "name": "redis-distributed-lock",
          "target_interface": "TLS Redis endpoint",
          "operation_type": "redis_connect",
          "target_repo": "redis/redis",
          "conditions": [
            "len(conf.DistributedLocks) != 0"
          ],
          "operations": [
            {
              "operation": "CREATE CLIENT",
              "trigger": "Build one TLS Redis client per configured distributed lock address and wrap them in Redsync",
              "conditions": [
                "len(conf.DistributedLocks) != 0"
              ],
              "implementation": "locks.NewLocks(conf.DistributedLocks...) -> redis.NewClient(... TLSConfig ...); redsync.New(...)"
            }
          ]
        }
      ]
    },
    "Runtime JiralertLifecycle.InitializeClinic": {
      "infrastructure": {
        "mysql": {
          "conditions": [
            "The code always attempts to create the GORM handle during startup; later DB operations only happen on specific request branches"
          ],
          "operations": [
            {
              "operation": "OPEN DB HANDLE",
              "trigger": "Prepare the shared GORM handle used later for cached alert inserts and tenant-kind lookups",
              "conditions": [
                "Always"
              ],
              "implementation": "gorm.Open(mysql.Open(conf.DBDSN), &gorm.Config{...})"
            }
          ]
        }
      },
      "services": []
    },
    "Runtime JiralertLifecycle.InitializeJiraClient": {
      "infrastructure": {},
      "services": []
    },
    "Runtime JiralertLifecycle.StartHTTPServer": {
      "infrastructure": {},
      "services": []
    },
    "HTTP POST /alert": {
      "infrastructure": {
        "mysql": {
          "conditions": [
            "Only on the disable_warning shortcut branch or the cluster/tenant enrichment branch"
          ],
          "operations": [
            {
              "operation": "INSERT",
              "trigger": "Persist the raw alert payload to jiralert_dev.cached_issues instead of touching Jira",
              "conditions": [
                "receiverConf.DisableWarning == true",
                "project template renders successfully"
              ],
              "implementation": "Receiver.Notify -> clinicCli.InsertAlertIntoTables(project, jsonData) -> regionDB.Exec(insertSQL, project, rawData)"
            },
            {
              "operation": "SELECT",
              "trigger": "Resolve tenant kind after the issue custom fields have been filled from tenant_id or cluster_id labels",
              "conditions": [
                "receiverConf.DisableWarning == false",
                "clinicCli != nil",
                "tenantID != \"\" or clusterID != \"\"",
                "clinicCli.GetKindNClusterNameByTenantIDAndClusterID(...) is reached"
              ],
              "implementation": "Receiver.fillClusterAndTenant -> clinicCli.GetKindNClusterNameByTenantIDAndClusterID -> regionDB.Raw(selectKindByTenantID, tenantInt).Scan(&kind)"
            }
          ]
        }
      },
      "services": [
        {
          "name": "jira-rest-api",
          "target_interface": "Jira issue search/create/update/comment endpoints",
          "operation_type": "http",
          "target_repo": "atlassian/jira",
          "conditions": [
            "request body decodes as Alertmanager data",
            "receiverConf != nil",
            "receiverConf.DisableWarning == false",
            "either the alert is processed immediately or it is later dequeued from the in-memory summary worker"
          ],
          "operations": [
            {
              "operation": "SEARCH ISSUES",
              "trigger": "Find a reusable issue by the generated group label before deciding whether to create, update, or reopen",
              "conditions": [
                "receiverConf.CreateOnly == false"
              ],
              "implementation": "Receiver.findIssueToReuse / findIssueLeader -> JiraAdapter.Search -> JiraClientExtend.Search"
            },
            {
              "operation": "UPDATE SUMMARY",
              "trigger": "Keep the reused issue summary in sync with the newly rendered summary",
              "conditions": [
                "reusable issue found",
                "issue.Summary != issueSummary"
              ],
              "implementation": "Receiver.findToReuse -> updateSummary -> JiraAdapter.UpdateSummary -> Issue.UpdateWithOptions"
            },
            {
              "operation": "UPDATE DESCRIPTION",
              "trigger": "Keep the reused issue description in sync with the newly rendered description",
              "conditions": [
                "reusable issue found",
                "issue.Description != issueDesc"
              ],
              "implementation": "Receiver.findToReuse -> updateDescription -> JiraAdapter.UpdateDescription -> Issue.UpdateWithOptions"
            },
            {
              "operation": "ADD COMMENT",
              "trigger": "Append changelog or precheck comments to an existing or newly created Jira issue",
              "conditions": [
                "Either receiverConf.CommentChangelog == true for a reused issue, or createIssue(...) returns a new issue and comments != \"\""
              ],
              "implementation": "Receiver.findToReuse / createIssue -> addComment -> JiraAdapter.AddComment -> Issue.AddComment"
            },
            {
              "operation": "AUTO RECOVER / REOPEN",
              "trigger": "Mark the Jira issue recovered or reopen it by updating the recovery custom field",
              "conditions": [
                "reusable issue found",
                "Either len(data.Alerts.Firing()) == 0 and receiverConf.AutoResolve != nil and issue is not done, or issue was previously done and should reopen"
              ],
              "implementation": "Receiver.findToReuse -> AutoRecover -> JiraAdapter.AutoRecover -> Issue.UpdateWithOptions"
            },
            {
              "operation": "CREATE ISSUE",
              "trigger": "Open a fresh Jira issue when no reusable issue was found and the alert still has firing instances",
              "conditions": [
                "len(data.Alerts.Firing()) > 0",
                "findToReuse(...) returned issue == nil"
              ],
              "implementation": "Receiver.createIssue -> create -> JiraAdapter.Create -> Issue.Create"
            }
          ]
        },
        {
          "name": "redis-distributed-lock",
          "target_interface": "TLS Redis endpoint",
          "operation_type": "redis_lock",
          "target_repo": "redis/redis",
          "conditions": [
            "receiverConf.DisableWarning == false",
            "len(receiverConf.CustomGroupLabels.Default) > 0 or len(receiverConf.CustomGroupLabels.Extended) > 0",
            "lks != nil",
            "findToReuse(...) did not already return an issue"
          ],
          "operations": [
            {
              "operation": "LOCK / UNLOCK",
              "trigger": "Serialize custom-group issue leadership selection before creating a new leader or sub-task",
              "conditions": [
                "custom aggregation labels resolve to at least one label",
                "distributed locks were initialized"
              ],
              "implementation": "Receiver.aggregateByLabels -> lks.LockWaitContext(ctx, customGroupLabel, expiryTime); defer lock.Unlock()"
            }
          ]
        },
        {
          "name": "precheck-api",
          "target_interface": "configured precheck API endpoint",
          "operation_type": "http_post",
          "target_repo": "unknown/external-api",
          "conditions": [
            "receiverConf.DisableWarning == false",
            "no reusable issue short-circuited the create path",
            "exactly one precheck target matched",
            "matched target.Source == api"
          ],
          "operations": [
            {
              "operation": "HTTP POST",
              "trigger": "Validate the first alert against the configured external API and optionally mutate Jira priority before issue creation",
              "conditions": [
                "params[\"api\"] is configured",
                "len(data.Alerts) >= 1"
              ],
              "implementation": "Receiver.precheck -> ValidateHandler.validateByAPI -> http.Client.Do(req)"
            }
          ]
        },
        {
          "name": "activity-hub",
          "target_interface": "GET {eventURL}{clusterID}/from/{from}/to/{to}",
          "operation_type": "http_get",
          "target_repo": "tidbcloud/activityhub",
          "conditions": [
            "receiverConf.DisableWarning == false",
            "no reusable issue short-circuited the create path",
            "exactly one precheck target matched",
            "matched target.Source == event"
          ],
          "operations": [
            {
              "operation": "HTTP GET",
              "trigger": "Fetch recent activity events for short-window and long-window precheck evaluation",
              "conditions": [
                "params[\"eventNames\"] is configured",
                "data.CommonLabels contains tidb_cluster_id or cluster_id"
              ],
              "implementation": "Receiver.precheck -> ValidateHandler.validateByEvent -> clinicCli.GetActivities -> http.Client.Do(req)"
            }
          ]
        },
        {
          "name": "clinic-info",
          "target_interface": "GET {clinicInfoAddr}{clusterID}",
          "operation_type": "http_get",
          "target_repo": "tidbcloud/clinic",
          "conditions": [
            "receiverConf.DisableWarning == false",
            "clinicCli != nil",
            "issue.CustomFields contains cluster or tenant metadata after fillClusterAndTenant normalization"
          ],
          "operations": [
            {
              "operation": "HTTP GET",
              "trigger": "Resolve tenant ID and cluster name from clinic info when cluster ID is available",
              "conditions": [
                "clusterID != \"\""
              ],
              "implementation": "Receiver.fillClusterAndTenant -> clinicCli.GetKindNClusterNameByTenantIDAndClusterID -> http.Client.Do(req)"
            }
          ]
        }
      ]
    },
    "HTTP GET /": {
      "infrastructure": {},
      "services": []
    },
    "HTTP GET /version": {
      "infrastructure": {},
      "services": []
    },
    "HTTP GET /config": {
      "infrastructure": {},
      "services": []
    },
    "HTTP GET /healthz": {
      "infrastructure": {},
      "services": []
    }
  },
  "notes": [
    "The immediate request path can terminate before any remote dependency call when the webhook JSON is invalid, the receiver is missing, or the alert is buffered because the per-summary limiter rejects immediate execution.",
    "Buffered alerts are processed later by a per-summary goroutine that re-enters the same notify.NewReceiver(...).Notify(...) flow; the same downstream Jira, precheck, and clinic dependencies therefore still belong to POST /alert, but only asynchronously."
  ]
}
