---
name: manage-release-changes
description: |
  List, inspect, create, update, and cancel Ops Portal Release Change records.
  Use for Change entries on /release/{release_id}, not Config Center changes.
  Does not submit, deploy, verify, or close a Change.
---

# OPS Release Change Management

Manage the Change records shown on an Ops Portal release page. Use the shared
`skills/platform/devops-api/scripts/devops_http.sh` transport; it adds
`/api/v1/devops` and authentication automatically.

This sub-skill manages Change metadata only. Do not use it to submit a draft,
execute or force a deployment, verify a deployment, or close a Change.

## Safety Rules

1. Execute reads immediately when they are in scope. Before every create,
   update, or cancel, show the user the target environment, method, path, and
   exact JSON payload (or state that the request has no body), then obtain
   explicit confirmation.
2. Never infer a write environment from a frontend URL alone. Require an
   explicit `prod`, `staging`, or `dev` choice for every mutation.
3. Default new Changes to `"is_draft": true`. Use `false` only when the user
   explicitly asks to enter the approval workflow and confirms that side effect.
4. Read the current Change immediately before an update. Preserve every
   deployment in the update payload; the controller replaces the complete
   deployment list even when the caller intended a partial update.
5. Verify every successful mutation with a follow-up GET and report the Change
   ID, status, and frontend URL.
6. Never test mutations in Production. Do not retry a failed mutation until a
   GET confirms whether the first request changed state.

## Environments and Frontend URLs

Production is the default only for reads.

| Environment | `--env` | Release URL | Change URL |
|---|---|---|---|
| Production | `prod` | `https://ops.tidbcloud.com/release/{release_id}` | `https://ops.tidbcloud.com/release/{release_id}/change/{change_id}` |
| Staging | `staging` | `https://ops-staging.tidbcloud.com/release/{release_id}` | `https://ops-staging.tidbcloud.com/release/{release_id}/change/{change_id}` |
| Dev | `dev` | `https://ops-dev.tidbcloud.com/release/{release_id}` | `https://ops-dev.tidbcloud.com/release/{release_id}/change/{change_id}` |

## Read Changes

### List Changes in a Release

```bash
RELEASE_ID=30083
skills/platform/devops-api/scripts/devops_http.sh \
  --env prod \
  --path "/release/releases/${RELEASE_ID}/changes"
```

`GET /release/releases/{release_id}/changes` returns `items`, ordered with open
Changes first and closed Changes last. Each item contains the basic Change
fields; related entities are not expanded by this list endpoint.

Useful local summaries:

```bash
skills/platform/devops-api/scripts/devops_http.sh \
  --env prod \
  --path "/release/releases/${RELEASE_ID}/changes" |
jq '[.items[] | {id, name, application_id, status, type, risk_level, tier}]'
```

### Get One Change

Use all expansion flags when preparing an update or when the user requests full
detail:

```bash
CHANGE_ID=62477
skills/platform/devops-api/scripts/devops_http.sh \
  --env prod \
  --path "/release/changes/${CHANGE_ID}" \
  --query "with_detail=true&with_change_items=true&with_release=true&with_applicant=true&with_application=true&with_tickets=true"
```

`GET /release/changes/{change_id}` returns:

| Field | Meaning |
|---|---|
| `change` | The Change record; expansions populate `detail`, `change_items`, `release`, `applicant`, and `application` |
| `release_tickets` | All tickets in the parent release when `with_tickets=true` |
| `errcode`, `errmsg`, `trace_id` | Shared response metadata |

Important Change fields include `id`, `release_id`, `application_id`, `name`,
`status`, `status_reason`, `type`, `tier`, `risk_level`, `window_start_at`,
`window_end_at`, `change_item_names`, `deployments`, `detail`, and `result`.

## Resolve Creation Inputs

Resolve inputs from the API instead of guessing names or timestamps.

### Release and tickets

```bash
skills/platform/devops-api/scripts/devops_http.sh \
  --env prod \
  --path "/release/releases/${RELEASE_ID}" \
  --query "with_tickets=true"
```

Use exact ticket `name` values from `release.tickets` for
`change_item_names`. A selected ticket must belong to the same release.

### Applications

```bash
skills/platform/devops-api/scripts/devops_http.sh \
  --env prod \
  --path /application/applications
```

Use an exact `items[].name` value for `application_name`.

### Default change window

```bash
APPLICATION_NAME=dataflow
skills/platform/devops-api/scripts/devops_http.sh \
  --env prod \
  --path /release/changes/default \
  --query "release_id=${RELEASE_ID}&application_name=${APPLICATION_NAME}"
```

The response contains `window_start_at` and `window_end_at` as Unix epoch
seconds. Confirm the intended timezone and display the converted local and UTC
times before a mutation; never send milliseconds.

## Fields and Enumerations

| Field | Accepted values or rule |
|---|---|
| `tier` | Non-empty array of `dedicated`, `serverless`, `premium` |
| `risk_level` | `low` or `high` |
| `type` | `sprint` or `hotfix` |
| `window_start_at`, `window_end_at` | Unix epoch seconds; start must precede end |
| `application_name` | Exact existing application name |
| `change_item_names` | Exact ticket names from the parent release |
| `applicant` | Existing user email; provide it explicitly when using an API token |
| `deployments[].order` | Zero-based execution order; keep entries ordered and unique |
| `deployments[].regions` | Non-empty list of affected region names |
| `deployments[].deploy_method` | `argocd`, `manual`, or `ee_api` |

Deployment method fields:

| Method | Required method-specific fields | Validation notes |
|---|---|---|
| `argocd` | `deploy_cd_link` | Must be an open GitHub pull-request URL of the form `https://github.com/{owner}/{repo}/pull/{number}` |
| `manual` | `deploy_description` | Describe the exact operator-run deployment procedure |
| `ee_api` | `deploy_cd_link`, `repo`, `branch`, `commit` | Used to create a Git tag and trigger an image build; preserve all four fields on update |

The response uses `method` inside a stored Change deployment, but create and
update requests use `deploy_method`. Convert stored deployment `info` back to
the request fields when constructing an update:

- `info.argocd.deploy_prs[0].link` -> `deploy_cd_link`
- `info.description` -> `deploy_description`
- `info.ee_api.ee_api_build_ng_image.{repo,branch,commit,pr_link}` ->
  `{repo,branch,commit,deploy_cd_link}`

## Create a Change

Endpoint: `POST /release/changes`.

Required workflow:

1. Confirm the environment and read the release, its tickets, the application
   list, and the default Change window.
2. Validate every ticket, application, deployment PR, region, timestamp, and
   applicant.
3. Build the full payload with `is_draft: true` unless the user explicitly asks
   otherwise.
4. Write the payload to a reviewable JSON file, show it to the user, and obtain
   explicit confirmation.
5. POST once. Capture `change_id`, GET the new Change, and verify its fields.

Example payload:

```json
{
  "name": "dataflow-service v1.2.3",
  "window_start_at": 1785862800,
  "window_end_at": 1785866400,
  "tier": ["dedicated"],
  "risk_level": "low",
  "need_outage": false,
  "note": "Release dataflow-service v1.2.3.",
  "rollback_plan": "Revert the deployment PR and restore the previous image tag.",
  "staging_test_report": "https://example.invalid/staging-report",
  "type": "sprint",
  "change_item_names": ["DM-1234"],
  "deployments": [
    {
      "order": 0,
      "regions": ["us-west-2"],
      "deploy_method": "argocd",
      "deploy_cd_link": "https://github.com/example/app-delivery/pull/1234",
      "deploy_description": ""
    }
  ],
  "release_id": 30083,
  "application_name": "dataflow",
  "is_draft": true,
  "applicant": "operator@example.com"
}
```

Execute only after confirmation:

```bash
skills/platform/devops-api/scripts/devops_http.sh \
  --env staging \
  --method POST \
  --path /release/changes \
  --body-file /tmp/release-change-create.json
```

The response contains `change_id`. With `is_draft=false`, the Change starts in
`created` and background processing may begin the approval workflow as soon as
its tickets are ready.

## Update a Change

Endpoint: `PUT /release/changes/{change_id}`.

Updates are supported only while status is `draft`, `created`, `approving`,
`withdrawn`, or `wait_for_deploy`. An update keeps a draft in `draft`; other
supported statuses transition to `created` and may restart approval processing.

Treat update as read-modify-write:

1. GET the Change with all expansion flags.
2. Refuse the update if its current status is not editable.
3. Convert every stored deployment into request format and apply only the
   requested field changes to the local desired state.
4. Include the complete desired `deployments` array in the update payload.
5. Show a before/after summary and the exact payload, then obtain explicit
   confirmation.
6. PUT once, GET again, and compare the persisted state to the desired state.

Although most top-level fields are optional, **omitting `deployments` does not
preserve them in the current controller**: it is decoded as an empty list and
replaces the stored deployments. Always include the full desired deployment
list.

```bash
skills/platform/devops-api/scripts/devops_http.sh \
  --env staging \
  --method PUT \
  --path "/release/changes/${CHANGE_ID}" \
  --body-file /tmp/release-change-update.json
```

Do not send create-only fields such as `release_id`, `application_name`,
`applicant`, or `is_draft`; the update API does not support changing them.

## Cancel a Change

Endpoint: `POST /release/changes/{change_id}/cancel`.

There is no `DELETE /release/changes/{change_id}` endpoint. Cancel is the
closest supported alternative, but it does not delete the record: it closes the
Change with result `cancel` and preserves its audit history. Treat cancel as
non-reversible.

Cancel is supported only while status is `draft`, `created`, `approving`,
`withdrawn`, or `wait_for_deploy`. Canceling an approving Change also attempts
to cancel its current approval.

Required workflow:

1. GET the Change and confirm its ID, name, release, environment, and current
   status with the user.
2. State explicitly that the record will remain and the operation cannot be
   undone through this API.
3. Obtain explicit confirmation, then send the bodyless POST once.
4. GET the Change again and verify `status == "closed"` and
   `result == "cancel"`.

```bash
skills/platform/devops-api/scripts/devops_http.sh \
  --env staging \
  --method POST \
  --path "/release/changes/${CHANGE_ID}/cancel"
```

## Explicitly Excluded Lifecycle Actions

Do not call these endpoints from this sub-skill:

- `/release/changes/{change_id}/submit`
- `/release/changes/{change_id}/deployments/{order}/ready`
- `/release/changes/{change_id}/deployments/{order}/deploy`
- `/release/changes/{change_id}/deployments/{order}/verify`
- `/release/changes/{change_id}/close`

Route a request for those lifecycle actions to a separately reviewed release
execution workflow. In particular, never interpret “update a Change” as
authorization to deploy it.
