From 61bc7bfb2ffdde9d795ee5741328d3ee724cae14 Mon Sep 17 00:00:00 2001 From: Jakub Majak Date: Wed, 16 Sep 2026 21:58:17 +0200 Subject: [PATCH 1/4] docs(cases): document case links, Olly provenance, and CLI limits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cases skill described the lifecycle but not the two things a triager hits first on an unfamiliar case: why it is quiet, and where it came from. Case relationships were undocumented. Links are one level and one parent, and linking a case as a child mutes it — so a child that never notifies is the design, not a delivery failure. A parent does not inline its children, so the skill shows the dataset query for them. Case provenance was undocumented because a case has no source field; it lives on indicators.genericIndicators[], which the analytics reference did not cover at all. That is also the only way to find cases Olly opened, since neither the CLI nor the API can filter by source. Adds a "what the CLI cannot do" table, because the gaps were previously only discoverable by trying: no create, no link, no mute, no list (that one was removed deliberately — discovery is a dataset query now), and labels are fixed at creation. Also documents eight dataset fields the reference omitted, including relationship, muteStatus, labelsFlat and lastReactivatedAt. Note relationship's discriminator is `type`, not the `$type` its sibling fields use. Co-Authored-By: Claude Opus 5 (1M context) --- skills/README.md | 2 +- skills/cx-cases/SKILL.md | 95 ++++++++++++++++++-- skills/cx-cases/references/case-analytics.md | 72 +++++++++++++++ 3 files changed, 161 insertions(+), 8 deletions(-) diff --git a/skills/README.md b/skills/README.md index e19a23bb..59356fc2 100644 --- a/skills/README.md +++ b/skills/README.md @@ -26,7 +26,7 @@ Supports Claude Code, Cursor, Codex, OpenCode, and [other supported agents](http | 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-cases` | Manage Coralogix Cases - inspect, assign/acknowledge/resolve/close, set priority overrides, read parent-child links and case provenance (discovery is via the cases dataset) | | `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 | diff --git a/skills/cx-cases/SKILL.md b/skills/cx-cases/SKILL.md index b0a66ca5..ee9b55c4 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,61 @@ 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`. It is absent for a +standalone case, `{"type": "PARENT"}` for a parent, and +`{"type": "CHILD", "parentCaseId": ""}` for a child — a parent does not +inline its children, so list those from the dataset: + +```bash +# Children of a parent case +cx dataprime "source system/labs.cases.state_updates \ + | dedupeby caseId orderby \$m.timestamp desc \ + | filter relationship != null && relationship.parentCaseId == '' \ + | choose caseId, caseNumber, title, status, priority" --from 24h +``` + +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 opened by an Olly scheduled task carries a generic indicator with +`indicatorType == "OLLY_SCHEDULED_TASK"`, plus a free-form `metadata` object naming +the task and run it came from. That also means you cannot filter for "Olly's cases" +with `cx cases` — it is a dataset query: + +```bash +# Cases opened by an Olly scheduled task +cx dataprime "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.metadata as source, createdAt \ + | orderby createdAt desc" --from 7d +``` + +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 +176,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) From b08866b5bf1e9c845fde4fc814afd0e079311818 Mon Sep 17 00:00:00 2001 From: Jakub Majak Date: Thu, 17 Sep 2026 09:33:32 +0200 Subject: [PATCH 2/4] docs(cases): move the DataPrime queries into the analytics reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The relationships and Olly-provenance sections carried their own `cx dataprime` blocks, duplicating queries the analytics reference already had. Dataset queries belong in one place, so SKILL.md now keeps only what a triager needs at the CLI — what a link means, why a child is quiet, what to read first — and links to the reference for the query. Co-Authored-By: Claude Opus 5 (1M context) --- skills/cx-cases/SKILL.md | 49 ++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/skills/cx-cases/SKILL.md b/skills/cx-cases/SKILL.md index ee9b55c4..7fc10378 100644 --- a/skills/cx-cases/SKILL.md +++ b/skills/cx-cases/SKILL.md @@ -121,18 +121,11 @@ Linking a case as a child **mutes it** by default, so the parent becomes the sin notifying case. Expect a child to be quiet; that is the design, not a delivery failure. -`cx cases get ` returns the case's `relationship`. It is absent for a -standalone case, `{"type": "PARENT"}` for a parent, and -`{"type": "CHILD", "parentCaseId": ""}` for a child — a parent does not -inline its children, so list those from the dataset: - -```bash -# Children of a parent case -cx dataprime "source system/labs.cases.state_updates \ - | dedupeby caseId orderby \$m.timestamp desc \ - | filter relationship != null && relationship.parentCaseId == '' \ - | choose caseId, caseNumber, title, status, priority" --from 24h -``` +`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. @@ -140,29 +133,21 @@ 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 opened by an Olly scheduled task carries a generic indicator with -`indicatorType == "OLLY_SCHEDULED_TASK"`, plus a free-form `metadata` object naming -the task and run it came from. That also means you cannot filter for "Olly's cases" -with `cx cases` — it is a dataset query: - -```bash -# Cases opened by an Olly scheduled task -cx dataprime "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.metadata as source, createdAt \ - | orderby createdAt desc" --from 7d -``` +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 `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. +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 From 76c82c1b7fc3a35fe2247bcb4988f7f964e1da2f Mon Sep 17 00:00:00 2001 From: Jakub Majak Date: Thu, 17 Sep 2026 09:42:23 +0200 Subject: [PATCH 3/4] docs(skills): pad the skills table Co-Authored-By: Claude Opus 5 (1M context) --- skills/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/skills/README.md b/skills/README.md index 59356fc2..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 - inspect, assign/acknowledge/resolve/close, set priority overrides, read parent-child links and case provenance (discovery is via the cases dataset) | -| `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 From f3457d7b0459d45d8789bddad1347dcd73a1c774 Mon Sep 17 00:00:00 2001 From: Jakub Majak Date: Thu, 17 Sep 2026 10:48:31 +0200 Subject: [PATCH 4/4] fix(deps): bump rustls to 0.23.45 for RUSTSEC-2026-0285 rustls 0.23.37 accepted TLS 1.3 handshake messages sent at the wrong encryption level when they followed a key-changing message in the same record, against RFC 8446 section 5.1. The transcript is still authenticated, so this does not let an attacker alter a handshake; the effect is that a peer can send messages in plaintext that should have been encrypted without rustls rejecting the connection. The advisory was published 2026-09-14, after the last scheduled audit run, so master has been failing Dependency Audit since. Patched in 0.23.45; `cargo update -p rustls` alone only reaches 0.23.43, so this pins precisely and pulls rustls-webpki 0.103.15 with it. Lockfile only. cargo check and the full test suite pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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]]