diff --git a/Cargo.lock b/Cargo.lock index f502bf29..4eaad529 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -317,7 +317,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -628,7 +628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -1702,14 +1702,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "once_cell", "ring", @@ -1758,7 +1758,7 @@ dependencies = [ "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -1769,9 +1769,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -2613,7 +2613,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/skills/README.md b/skills/README.md index e19a23bb..b3e77106 100644 --- a/skills/README.md +++ b/skills/README.md @@ -23,15 +23,15 @@ Supports Claude Code, Cursor, Codex, OpenCode, and [other supported agents](http ### Workflow -| Skill | Description | -|---|---| -| `cx-cost-optimization` | Analyze and reduce Coralogix data costs - usage analysis, TCO policies, retention, archive | -| `cx-cases` | Manage Coralogix Cases - list, inspect, assign/acknowledge/resolve/close, set priority overrides | -| `cx-slos` | Manage SLO definitions - list, inspect, check error budgets, create/update/delete | -| `cx-data-pipeline` | Configure data processing - parsing rules, enrichments, Events2Metrics, recording rules | -| `cx-platform-admin` | Manage access and security - API keys, roles, users, groups, IP access | -| `cx-observability-setup` | Set up monitoring - saved views, webhooks, notifications, integrations | -| `cx-cli` | Cross-cutting `cx` behavior that applies to every command, such as update notifications | +| Skill | Description | +|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `cx-cost-optimization` | Analyze and reduce Coralogix data costs - usage analysis, TCO policies, retention, archive | +| `cx-cases` | Manage Coralogix Cases - list, inspect, assign/acknowledge/resolve/close, set priority overrides, read parent-child links and case provenance | +| `cx-slos` | Manage SLO definitions - list, inspect, check error budgets, create/update/delete | +| `cx-data-pipeline` | Configure data processing - parsing rules, enrichments, Events2Metrics, recording rules | +| `cx-platform-admin` | Manage access and security - API keys, roles, users, groups, IP access | +| `cx-observability-setup` | Set up monitoring - saved views, webhooks, notifications, integrations | +| `cx-cli` | Cross-cutting `cx` behavior that applies to every command, such as update notifications | ## Requirements diff --git a/skills/cx-cases/SKILL.md b/skills/cx-cases/SKILL.md index b0a66ca5..7fc10378 100644 --- a/skills/cx-cases/SKILL.md +++ b/skills/cx-cases/SKILL.md @@ -1,18 +1,23 @@ --- name: cx-cases description: > - Triage and manage Coralogix Cases with the `cx cases` CLI — e.g. acknowledge, - assign, resolve, or re-prioritize a case, or inspect its event timeline or - notification deliveries. + This skill should be used when the user asks to "triage a case", "acknowledge a + case", "assign a case", "resolve a case", "close a case", "comment on a case", + "re-prioritize a case", "check case status", "see a case timeline", "check case + notifications", "create a case", "open a case", "link cases", "find the parent + case", "find child cases", "find cases Olly opened", "find cases from a scheduled + task", or wants to inspect or drive Coralogix Cases through their lifecycle using + the cx CLI. metadata: - version: "0.1.0" + version: "0.2.0" --- # Cases Management Skill -A **Case** groups related alert events into one investigation unit with a status, -priority, category, and assignee. Use this skill to inspect cases and drive them -through their lifecycle (active → acknowledged → resolved → closed). +A **Case** groups related indicators — usually alert events, sometimes an external +source — into one investigation unit with a status, priority, category, and +assignee. Use this skill to inspect cases and drive them through their lifecycle +(active → acknowledged → resolved → closed). ## CLI Commands @@ -33,6 +38,20 @@ through their lifecycle (active → acknowledged → resolved → closed). | `cx cases events get ` | A single event — drill in, e.g. to expand a comment thread | | `cx cases notifications [ ...]` | Notification deliveries (connector, status, time) | +## What the CLI cannot do + +Tell the user plainly and point at the alternative — do not improvise a command +that does not exist. + +| Not available | Why, and what to do instead | +|---|---| +| **Creating a case** | Cases are opened by the Cases service from an indicator — an alert event, an Error Tracking issue, or an Olly scheduled task. Create exists only on the *internal* API, so there is no `cx cases create`. To get a case for something: ask Olly to open one (`cx olly`, which can), fix the alert that should have caught it (`cx-alerts`), or open one in the Cases UI. | +| **Listing / searching cases** | `cx cases list` was removed. Case discovery lives in the dataset: query `system/labs.cases.state_updates` with `cx dataprime`. See [`references/case-analytics.md`](references/case-analytics.md). | +| **Linking / unlinking cases** | Parent-child links are created in the Cases UI or through the API, not the CLI. You can still *read* the hierarchy from the dataset — see "Case relationships" below. | +| **Muting a case** | No CLI verb. Mute in the UI. Muting stops notifications without changing status, which is the honest alternative to resolving work nobody has finished. | +| **Changing labels** | Labels are fixed when the case is created. `cx cases update` patches only the title and resolution reason. | +| **Reopening** | `RESOLVED` → `CLOSED` only. A recurrence of the same underlying indicator reopens the case automatically; a person cannot. | + ## Case Lifecycle ``` @@ -90,6 +109,46 @@ If uncertain, stay in `ACKNOWLEDGED` (reversible via `unacknowledge`) until confident. For non-resolution edits (title, post-hoc postmortem link), use `cx cases update`. +## Case relationships (parent / child) + +Cases can be linked into a **one-level** parent-child hierarchy: the parent is the +incident of record and children hang under it. A case has at most one parent, is +either a parent or a child but never both, and a parent has at most 100 children — +so there are no grandparents and no cycles. There is no "related" or "duplicate" +link type. + +Linking a case as a child **mutes it** by default, so the parent becomes the single +notifying case. Expect a child to be quiet; that is the design, not a delivery +failure. + +`cx cases get ` returns the case's `relationship`: absent for a standalone case, +`{"type": "PARENT"}` for a parent, `{"type": "CHILD", "parentCaseId": ""}` for +a child. A parent does not inline its children — to list them, see +[Case relationships](references/case-analytics.md#case-relationships-parent--child) +in the analytics reference. + +When triaging a child, read the parent first — the investigation usually lives +there. When resolving a parent, check its children: they do not resolve with it. + +## Cases opened by Olly + +A case has **no top-level source field**. Provenance lives on its indicators, so a +case Olly opened carries a generic indicator with +`indicatorType == "OLLY_SCHEDULED_TASK"` and a free-form `metadata` object naming the +task and run it came from. That also means `cx cases` cannot filter for Olly's cases +— see +[Generic indicators](references/case-analytics.md#generic-indicators--non-alert-sources-including-olly) +in the analytics reference for the query. + +Triage one the same way as any other case, with one addition: open the run link in +the indicator's `metadata` to see the evidence Olly based the case on, and judge that +evidence rather than taking the case's own summary at face value. + +The indicator's `externalId` is the dedup key. One unresolved case exists per key, and +a recurrence reopens the resolved case rather than forking a new one — so a case with +a non-null `lastReactivatedAt` is a repeat, which is usually the more interesting +signal than the current occurrence. + ## Bulk Operations There are no bulk endpoints. To act on many cases, pipe IDs through a loop, e.g. @@ -102,6 +161,13 @@ There are no bulk endpoints. To act on many cases, pipe IDs through a loop, e.g. for false alarms `close` from `ACTIVE` directly. - **Always supply a resolution reason** unless `--no-reason` truly applies. - **`P1`-style shorthand** is accepted anywhere a priority/status/category is expected. +- **Acknowledging means a person is on it** — escalation, PagerDuty and ServiceNow + all read that fact, so never acknowledge on someone's behalf to quiet a case. + Assign it, or mute it in the UI. +- **Prefer muting over resolving** when the work is real but nobody is on it. A + resolved case is a claim the system is healthy. +- **A case has no source field** — provenance is on its indicators. See + "Cases opened by Olly". - **Multi-profile fan-out** with `-p ` (repeatable) for cross-environment triage. - **Link to a specific case** — build `/cases?id=`, where `` is the console URL already seen in a `View in Coralogix: diff --git a/skills/cx-cases/references/case-analytics.md b/skills/cx-cases/references/case-analytics.md index d8a3e613..5ba3ce06 100644 --- a/skills/cx-cases/references/case-analytics.md +++ b/skills/cx-cases/references/case-analytics.md @@ -195,6 +195,65 @@ Per-element fields: | `state` | `TRIGGERED` (still firing) / `RESOLVED` / `NO_DATA` (signal dropped, **not** a recovery) / `MUTED`. | | `triggeredAt` / `resolvedAt` | Indicator-side timing (different from case-level resolution). | +#### Generic indicators — non-alert sources, including Olly + +`indicators.genericIndicators[]` — indicators from a source that is not a Coralogix alert. This is where a case's non-alert provenance lives; there is **no top-level `source` field on a case**, so this array is the only way to tell what opened it. + +| Field | Meaning | +|---|---| +| `indicatorType` | `ERROR_TRACKING` or `OLLY_SCHEDULED_TASK`. | +| `externalId` | The originating system's stable key for the problem. It is also the dedup key: one unresolved case per `(team, indicatorType, externalId)`, and a recurrence reopens rather than forking. | +| `instanceId` | The indicator's own UUID. | +| `status` | `TRIGGERED` / `RESOLVED`. | +| `priority` | `P1`–`P5`, indicator-side (independent of case priority). | +| `labels` | Flat `{key: value}` map. | +| `metadata` | **Free-form JSON object**, shaped by the producing system — not a string map, so values may be nested objects, numbers or lists. For an Olly scheduled task it carries the task and run the finding came from (`taskId`, `runUrl` and similar). Deliberately separate from `labels` so source provenance does not pollute the routing namespace. | +| `entityLinks` | URLs to the originating entity. | +| `lastTriggeredAt` / `lastResolvedAt` | Indicator-side timing. | + +`metadata` is set at creation and there is no API to change it afterwards, so treat it as a fixed record of where the case came from. + +**Cases opened by an Olly scheduled task, most recent first:** + +``` +source system/labs.cases.state_updates + | dedupeby caseId orderby $m.timestamp desc + | explode indicators.genericIndicators into gi + | filter gi.indicatorType == 'OLLY_SCHEDULED_TASK' + | choose caseId, caseNumber, title, status, priority, + gi.externalId as dedup_key, gi.metadata as source_metadata, createdAt + | orderby createdAt desc +``` + +To narrow to one task, reach into the metadata (`filter gi.metadata.taskId == ''`) — but confirm the key name from a sample row first, since the shape is owned by the producer, not by this schema. + +#### Case relationships (parent / child) + +`relationship` — the case's place in a parent-child hierarchy. **Absent entirely for a standalone case**, so test for null before reading it. + +| Shape | Meaning | +|---|---| +| `{"type": "PARENT"}` | This case is a parent. Its children are **not** inlined — find them by querying for cases whose `relationship.parentCaseId` is this `caseId`. | +| `{"type": "CHILD", "parentCaseId": ""}` | This case is a child of that case. | + +Note the discriminator is **`type`**, not `$type` — unlike `metadata.change`, `assignee` and `closedBy`, which use `$type`. This is the easiest thing to get wrong here. + +Structural guarantees, which make these queries simpler than they look: a case has **at most one parent**, is **either a parent or a child but never both**, and a parent has **at most 100 children**. The hierarchy is therefore exactly one level deep and cannot contain cycles. + +**A parent and its children in one result:** + +``` +source system/labs.cases.state_updates + | dedupeby caseId orderby $m.timestamp desc + | filter relationship != null + | filter caseId == '' || relationship.parentCaseId == '' + | choose caseId, caseNumber, title, status, priority, relationship.type as rel +``` + +**Linking is not visible as its own event.** A link emits a state-update on both cases with `metadata.trigger == 'caseUpdated'` and no `metadata.change`, so you cannot find "when was this linked" by filtering on a trigger. Detect links from the current `relationship` value, or by diffing it across rows ordered by `$m.timestamp`. + +`muteStatus` is the related signal: linking a case as a child mutes it by default, and a child muted that way records a mute source of `AUTOMATIC_LINKING`. + #### Permutations & labels (routing / attribution) `permutations` — the actual observed label combinations that produced this case. Each element of `permutations` is a **permutation-group**: a list of `{key, value, permutationIndex}` objects representing one real co-occurring set of key/value pairs. @@ -223,6 +282,19 @@ Per-element fields: #### Other fields `aiSummary` is a pre-computed string. Treat it as one input among many — never the source of truth. + +Also present on every row, and worth knowing because they are easy to miss: + +| Field | Meaning | +|---|---| +| `caseUrl` | Deep link to the case in the console. Prefer it over building a URL by hand. | +| `labelsFlat` | The case's labels as flat `key=value` strings, which is usually easier to filter on than the nested `labels` map. | +| `muteStatus` | Whether notifications are suppressed, and why. A child case muted by linking records a source of `AUTOMATIC_LINKING`. | +| `resolutionCode` | The structured reason a case was resolved, alongside the free-text `resolutionDetails.resolutionReason`. | +| `lastReactivatedAt` | Set when a resolved case was reopened by a recurrence of the same indicator. Non-null means this case has fired more than once. | +| `ollyAnalysis` | Olly's stored analysis of the case, when one has been generated. | +| `schemaVersion` | Payload version. Check it before relying on a field this reference does not list. | + `$m.timestamp` is the **event timestamp** (when the state-update was emitted) and is what you order by. `createdAt` (or `$d.createdAt`) is the **case creation time**. ## Hard rules (ALWAYS apply)