From 5a8a4472fb67d1227e01408270d0b009b4c90d9d Mon Sep 17 00:00:00 2001 From: Amitava Saha Date: Sun, 13 Sep 2026 06:42:46 +0530 Subject: [PATCH 1/3] alert docs: a group-bound silence does not scope what it mutes The docs said a silence created from an alert group had a Scope of "Group-bound", which promised a restriction that does not exist. The group binding only controls when the silence ends: on resolve, the silence's expiry is moved to that moment so it lifts early. Matching never reads it, so the silence mutes every alert in the workspace whose labels match its matchers, group member or not. This caused a support question, so it is now stated directly in the anatomy table, in a caution on the group flow, and on the alert-groups page. The Scope column is now Ends, with At expiry and When the group resolves. "From an Alert Group" also claimed the group's labels were pre-filled as matchers. That read alarm_groups.labels, which holds only the distinctive k=v a correlated group matched on and is empty for most groups, so nothing pre-filled. Matchers are now derived server-side from the routing rule's group_by keys, the value every live member shares, then alarm_id. A group whose alerts share no label pre-fills nothing and offers its alerts' labels as chips with per-label coverage. Adding every offered chip does not scope the silence to the group, and the page now says why with the measured numbers: a folder label on 10 of 17 alerts takes the match down to 10 and leaves 7 notifying, while two multi-valued keys accept their cartesian product. The "Alerts this silence will mute" preview is the honest check. Also documents the Alerts muted column and its row expansion, and the Silenced badge now linking to whatever is muting the alert. Co-Authored-By: Claude Opus 5 --- src/content/docs/docs/alerts/alert-groups.mdx | 2 +- src/content/docs/docs/alerts/silences.mdx | 28 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/content/docs/docs/alerts/alert-groups.mdx b/src/content/docs/docs/alerts/alert-groups.mdx index 7f985c0e..3785d7e8 100644 --- a/src/content/docs/docs/alerts/alert-groups.mdx +++ b/src/content/docs/docs/alerts/alert-groups.mdx @@ -62,7 +62,7 @@ Click any row to open the group detail page. - **Title:** set by the first alert and can't be changed. - **Run RCA / View RCA:** starts or opens a root-cause investigation for the group. See [Run RCA and View RCA](#run-rca-and-view-rca). - **Ask KloudMate Assistant:** opens the assistant chat panel with the group's labels, state, alert and instance counts, and routing rule pre-loaded into the prompt, so you can start investigating without retyping context. -- **Silence this group:** opens the silence creator pre-filled with the group's labels as matchers, bound to the group so the silence expires when it resolves. +- **Silence this group:** opens the silence creator with matchers derived from the group, bound to the group so the silence lifts when the group resolves. The binding sets when the silence ends, not what it mutes: the silence covers any alert in the workspace whose labels match its matchers, group member or not. See [From an Alert Group](../silences/#from-an-alert-group). ### Firing instances diff --git a/src/content/docs/docs/alerts/silences.mdx b/src/content/docs/docs/alerts/silences.mdx index 6e1c4197..7af12fa3 100644 --- a/src/content/docs/docs/alerts/silences.mdx +++ b/src/content/docs/docs/alerts/silences.mdx @@ -24,7 +24,7 @@ Silences are grouped into **Active**, **Expired**, and **All** tabs. | **Matchers** | Label conditions that pick which alerts the silence applies to. Same operator set as [Routing Rules](../routing-rules/#matchers): Equals, Not equals, Matches regex, Doesn't match regex. | | **Expires at** | Date and time the silence stops. Capped at 30 days from creation. | | **Reason** | Free-form text; recommended so others know why the silence exists. | -| **Scope** | Either **Group-bound** (auto-expires when a specific [Alert Group](../alert-groups/) resolves) or **Standalone** (lives until it expires). | +| **Ends** | **At expiry**, or **When the group resolves** for a silence bound to an [Alert Group](../alert-groups/). The binding sets when the silence ends, never which alerts it mutes. | ## Create a silence @@ -40,17 +40,35 @@ Create a silence from the Silences page, an alert group, or an alert's Pause Not ### From an Alert Group -On any [Alert Group](../alert-groups/) detail page, click **Silence this group**. The silence creator opens pre-filled with: +On any [Alert Group](../alert-groups/) detail page, click **Silence this group**. The silence creator opens with matchers derived from the group and a binding to it. -- The group's labels populated as matchers. -- A binding to the group, so the silence stops automatically when the group resolves. +:::caution[A group binding sets when the silence ends, not what it mutes] +The binding does one thing: when the bound group resolves, the silence's expiry moves to that moment and it lifts early. Matching never looks at it, so the silence mutes every alert in the workspace whose labels match its matchers, group member or not. +::: -Save it; this creates a **Group-bound** silence. +**How the matchers are filled in.** They come from the routing rule's group-by keys, taking the value the group carries or the value every live member shares. A rule that groups by nothing falls back to `alarm_id`, so most groups pre-fill with a single matcher such as `alarm_id = alm_7f3c2b`. + +**When the group's alerts share no label.** A silence is a set of label matchers that all have to hold, so it can't pick out an arbitrary list of alerts. A group whose alerts share nothing pre-fills no matchers. Instead, the form offers the labels those alerts do carry as one-click chips, each showing how many of the group's alerts it covers, such as `10/17`. + +#### Adding every offered label doesn't scope the silence to the group + +Building a silence out of the group's own labels fails in both directions. + +- **It mutes less than the group.** An alert has to match every matcher, so adding a key that only some members carry drops the rest. On a 17-alert group, a folder label present on 10 of them takes the match from 17 down to 10, and the other 7 keep notifying. +- **It still mutes outside the group.** Two multi-valued keys accept every combination of the two, not just the pairs the group holds. Three rules across seven hosts makes 21 combinations. The group holds 17 alerts. + +Use the **Alerts this silence will mute** preview on the create form instead. It counts every alert instance the current matchers would mute, before you save. ### From an alert's Pause Notifications On an alert rule's **more options (⋯) → Pause Notifications**, KloudMate creates a silence scoped to that alert (an `alarm_id` matcher) with an expiry: the alert keeps evaluating, and only its notifications are suppressed. While paused, the rule is marked **Silenced**; view or end the pause from **Alerts → Silences**. Add matchers to narrow the pause to specific instances. +## See what a silence is muting + +The Silences list carries an **Alerts muted** column: how many alert rules each active silence is muting. Expand a row to list those rules, each marked with a chip such as `3 firing` when its instances are firing. The silence detail page shows the same list, along with a link to the group the silence is bound to. + +In the other direction, the **Silenced** badge links to the silence or maintenance window muting that alert. When more than one is muting it, the badge opens a menu listing all of them. + ## Silence vs. maintenance window Both features are a **notification gate, not a state change**: the alert still evaluates on its schedule and its state transitions are still recorded in history; only the notification is withheld. Silencing every firing instance of an alert group keeps the group **Open** (shown as **Muted**); it does **not** resolve the group, and muting never emits a *resolved* notification. The differences between the two are operational, not behavioral: From 6dbd28ac7fac7d8c16fdffd5a8a95abd7481ef78 Mon Sep 17 00:00:00 2001 From: Amitava Saha Date: Sun, 13 Sep 2026 06:42:54 +0530 Subject: [PATCH 2/3] alert docs: export and import alert rules as JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds alerts/export-and-import.mdx for moving rules between workspaces: Export as JSON on a rule row, Export alerts as JSON on a folder header, and Import JSON on the Alerts list. Wired into the create-alerts row actions, the folders kebab list and Related, and the alerts index cards. Covers what does not survive the move, which is not visible from the UI: the folder, the Dashboard and Panel links, and Smart Alerts ownership, since stripping the internal __…__ annotations drops __km_source__ and the detector stops managing the copy. Folder inheritance does survive, so an imported rule runs on system defaults until it is put in a folder. AWS is the one thing import has to rebind. An account connects to KloudMate once, so the exported account id never exists in the destination: one account binds automatically, several needs a choice, none imports the rules paused. Region, namespace and dimensions are copied verbatim and need review. Also covers the retry path for a partial import and the fact that nothing dedupes, so importing the same JSON twice duplicates every rule. Source is kloudmate-frontend a301266f, which is frontend-only and already in master, so nothing here is release-gated. Co-Authored-By: Claude Opus 5 --- .../docs/docs/alerts/create-alerts.mdx | 1 + .../docs/docs/alerts/export-and-import.mdx | 63 +++++++++++++++++++ src/content/docs/docs/alerts/folders.mdx | 3 +- src/content/docs/docs/alerts/index.mdx | 1 + 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/content/docs/docs/alerts/export-and-import.mdx diff --git a/src/content/docs/docs/alerts/create-alerts.mdx b/src/content/docs/docs/alerts/create-alerts.mdx index bcc9aa5e..144fcce0 100644 --- a/src/content/docs/docs/alerts/create-alerts.mdx +++ b/src/content/docs/docs/alerts/create-alerts.mdx @@ -20,6 +20,7 @@ From the **more options (⋯)** icon on any rule, you can: - **Edit** the rule configuration - **Duplicate** the rule - **Move to folder** to move the rule into a different [folder](../folders/) +- **Export as JSON** to copy the rule into another workspace. See [Export and Import Alerts](../export-and-import/) - **View state history** for the rule - **Pause Evaluation** or **Pause Notifications** - **Delete** the rule diff --git a/src/content/docs/docs/alerts/export-and-import.mdx b/src/content/docs/docs/alerts/export-and-import.mdx new file mode 100644 index 00000000..3b92f4ee --- /dev/null +++ b/src/content/docs/docs/alerts/export-and-import.mdx @@ -0,0 +1,63 @@ +--- +title: "Export and Import Alerts" +description: "Copy alert rules into another workspace by exporting them as JSON and pasting them in." +sidebar: + order: 2.8 +--- + +Alert rules can be exported as JSON and moved between workspaces. Export a single rule or an entire folder, then paste it into another workspace to reuse the same rules across staging and production, or quickly create a new workspace from an existing setup. + +Importing creates new rules, so it requires the same permission as creating an alert rule by hand. + +## Export a rule or a folder + +To export a single rule, open the **more options (⋯)** menu on its row and select **Export as JSON**. + +To export an entire folder, switch the Alerts list to **Folder view**, open the folder header's kebab menu, and select **Export alerts as JSON**. The export includes every rule in the folder, including any that the search box or state filter is hiding. + +## What is not copied + +The export carries the rule's definition: name, description, queries, condition, evaluation and recovery periods, tags, and annotations. Some parts of a rule belong to the workspace it came from and are not copied: + +- **The folder.** Imported rules arrive ungrouped. +- **The Dashboard and Panel links.** They point at a dashboard in the source workspace, so select a dashboard in the destination after importing. +- **Smart Alerts ownership.** A rule exported from [Smart Alerts](../smart-alerts/) imports as an ordinary rule, and the detector that created it no longer manages the copy. + +A rule that inherited its evaluation interval, no-data state, or eval-error state from a folder continues to inherit after the import. Until it's moved into a folder in the destination workspace, it falls back to the system defaults. See [Folders](../folders/#inheritance-and-overrides). + +A rule that was paused when it was exported stays paused after the import. + +## Import rules into a workspace + +1. Open **Alerts** in the destination workspace. +2. Click **Import JSON** and paste the exported JSON. +3. If the rules query AWS, select an account. +4. Click **Import**. + +The import doesn't check for rules that already exist in the destination, so importing the same JSON twice creates a duplicate of every rule. + +### AWS queries need an account in the destination + +An AWS account connects to KloudMate exactly once, so the account an exported rule refers to will never exist in the destination workspace. Rules that query CloudWatch have to be pointed at an account in the workspace they're imported into: + +- **One account in the workspace:** the rules are bound to it automatically. +- **Several accounts:** select which one to use before importing. +- **No account connected:** the rules are imported **paused**. Connect an account, point each rule at it, then resume evaluation. + +Region, namespace, and dimensions are copied unchanged, so they still refer to resources in the source account. Review and update them after importing. + +### Retry a partial import + +An import that fails partway through keeps the rules it already created, and a retry sends only the ones that failed. The usual cause is the plan's alert rule limit, in which case the rules that fit are created and the rest are reported by name. + +## Edit the JSON before importing + +The export is plain text, so you can edit it before importing. Delete entries from the `alerts` array to import a subset, or rename rules to avoid name collisions with rules already in the destination. The import also accepts a bare array of alerts or a single alert object, so a fragment copied from a larger export works on its own. + +An export created by a newer version of KloudMate can't be imported. + +## Related + +- [Creating Alerts](../create-alerts/): building a rule from scratch, from a template, or with AI. +- [Folders](../folders/): grouping imported rules and giving them shared defaults. +- [Smart Alerts](../smart-alerts/): curated detectors that create and maintain rules for you. diff --git a/src/content/docs/docs/alerts/folders.mdx b/src/content/docs/docs/alerts/folders.mdx index 8228b658..522286f2 100644 --- a/src/content/docs/docs/alerts/folders.mdx +++ b/src/content/docs/docs/alerts/folders.mdx @@ -25,7 +25,7 @@ You create folders inline from the alert rule editor's **Folder** picker; there' ## Edit folder defaults -In Folder view, each folder header row has a kebab menu with **Edit folder** and **Delete folder**. +In Folder view, each folder header row has a kebab menu with **Edit folder**, **Export alerts as JSON**, and **Delete folder**. **Edit folder** opens a dialog with the folder's settings: @@ -80,3 +80,4 @@ A rule's folder identity flows into the alert's labels as `alarm_rule_folder_id` - [Creating Alerts](../create-alerts/): picking a folder during rule creation. - [Routing Rules](../routing-rules/): matching on folder labels. +- [Export and Import Alerts](../export-and-import/): exporting a folder's rules into another workspace. diff --git a/src/content/docs/docs/alerts/index.mdx b/src/content/docs/docs/alerts/index.mdx index 2420c5a2..510f495e 100644 --- a/src/content/docs/docs/alerts/index.mdx +++ b/src/content/docs/docs/alerts/index.mdx @@ -83,6 +83,7 @@ For how these states transition, and how the pending duration, recovery period, + From 2708538563aa01cf836221110957b94036524d0f Mon Sep 17 00:00:00 2001 From: Amitava Saha Date: Sun, 13 Sep 2026 06:43:01 +0530 Subject: [PATCH 3/3] CLAUDE.md: trust the reader, and do not clip what survives the cut Two rules that had to be given in review rather than read off the file. "Trust the reader" covers three habits: describing what a control displays ("the button shows the number it will create"), announcing a list before showing it ("it leaves out everything that can't survive the move", above a table of exactly that), and justifying what the reader can infer ("the rules import paused, so they don't fire against an account they can't read"). The test is whether deleting the sentence changes what a competent reader does. The sentence-shape section gains the maintainer's own rewrite of an opening paragraph as the reference: name the capability outright, keep full determiners, use the verb that names the reader's goal, and drop vague pronoun tails. Trimming fluff and writing complete sentences pull in opposite directions without this, which is why the first pass over-corrected into clipped prose. Also notes that product jargon in context is fine; it is the ornamental register around it that reads as AI. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 3deca6bd..6cb8fb5b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,6 +51,36 @@ Cut on sight: | "When nothing in the range carries the key, the tab falls back and says so: `Nothing in this range carries category…`" | "If you group by a key that isn't present in the selected range, the grouping falls back to event name." | | "Journeys resolves it over whole sessions through a semi-join." | "In a funnel, `cart_value > 100` selects sessions that emitted at least one matching event." | +### Trust the reader + +The reader is competent and has the product open. Anything they can see on screen, work out for themselves, or would never have assumed otherwise is not worth a sentence. Three habits that keep coming back: + +**Don't describe what a control displays.** Give the instruction or the rule. The reader will read the button. + +| Don't write | Write instead | +|---|---| +| "Click **Import**. The button shows the number it will create, for example **Import 4 alerts**." | "Click **Import**." | +| "It reads **None** when nothing matches, and `—` for an expired silence." | (cut it) | +| "Paste the JSON. The dialog checks it as you paste and lists the alerts it found by name." | "Paste the exported JSON." | +| "Either way a dialog shows the JSON. Click **Copy JSON**, then paste it into the destination." | "Copy the JSON into the destination workspace." | + +**Don't announce a list before showing it.** A lead-in that only says "a list is coming" is throat-clearing. Delete it and let the list or table do the work. + +| Don't write | Write instead | +|---|---| +| "It leaves out everything that can't survive the move." (above a table of what's dropped) | (cut it) | +| "The silence creator opens pre-filled with:" (above two bullets) | "The silence creator opens with matchers derived from the group and a binding to it." | + +**Don't justify what the reader can infer.** State the fact and stop. Reasoning that only leads back to the fact is padding. + +| Don't write | Write instead | +|---|---| +| "the rules import **paused**, so they don't fire against an account they can't read" | "the rules import **paused**" | +| "The rule's ID and folder are dropped, because both belong to the source workspace." | "Imported rules land ungrouped." | +| "The rules appear in the Alerts list right away." | (cut it) | + +The test: delete the sentence and ask whether a competent reader does anything differently. If not, it stays deleted. + ### Sentence shape One idea per sentence, subject first, verb early. Any clause the reader has to unpack reads as machine writing, and so does a technical thing described in literary paraphrase. @@ -68,6 +98,21 @@ Name things with the term the product and the reader both use. "Anything the eve Plain does not mean cryptic. Cutting a sentence to four words that the reader then has to decode is a worse failure than the padding it replaced. Write the full sentence, then delete only what carries no meaning. +The maintainer rewrote this opening as the model for how a sentence should be built. Match it: + +> **Drafted:** "Alert rules move between workspaces as JSON. Export a rule or a whole folder, then paste it into another workspace to run the same rules in staging and production, or to set up a new workspace from one you already have." +> +> **Rewritten:** "Alert rules can be exported as JSON and moved between workspaces. Export a single rule or an entire folder, then paste it into another workspace to reuse the same rules across staging and production, or quickly create a new workspace from an existing setup." + +What changed, and what to copy: + +- **Name the capability outright.** "can be exported as JSON and moved between workspaces" states both actions in the order the reader performs them. "move between workspaces as JSON" compresses them into a clause the reader has to unpack, and buries the verb ("export") they're searching for. +- **Keep full determiners.** "a single rule or an entire folder", not "a rule or a whole folder". Clipping articles and quantifiers reads as note-taking, not prose. +- **Use the verb that names the reader's goal.** "reuse the same rules across staging and production" and "create a new workspace", not the vaguer "run" and "set up". +- **No vague pronoun tails.** "from an existing setup", not "from one you already have". The same applies to "one", "ones", and "them" standing in for a noun the reader then has to resolve: "pick the destination's after importing" becomes "select a dashboard in the destination after importing". + +Trimming fluff (the section above) and writing complete sentences are not in tension. Cut the sentence that carries nothing; write the sentences that remain in full. + Detail has a bar too: state the behavior, not its operator-by-operator mechanics. "Selecting more than one value on a field matches any of them" is the rule; "two `=` picks become **In**, two `!=` picks become **Not in**, and a second `>` replaces the first" is a spec dump. ### Tone (AGENTS.md §2 has the full word lists; these are the repeat offenders) @@ -76,6 +121,7 @@ Detail has a bar too: state the behavior, not its operator-by-operator mechanics - **No drama or voiceover:** rhetorical build-ups ("X is the answer"), trailing tags ("…, which it is"), figurative labels ("delivery vehicle"), cutesy asides ("the same treatment for your Lambda functions"). Write the plain fact. - **No reflexive benefit-tails:** don't glue "…, so you can [vague upside]" onto every sentence. Keep it only when the cause and effect is real and specific. - **No hype or filler:** powerful, seamless, robust, effortless; leverage/utilize → use, via → with, "in order to" → to, "simply/just/easily". +- **Jargon in context is fine; ornamental vocabulary is not.** `matcher`, `group-by key`, `eBPF`, and `alarm_id` are the real names for real things, so use them. What reads as AI is the decorative register around them: "the button narrows to **Retry**", "the reliable check", "everything that can't survive the move". Write the word a colleague would say out loud. ### Do this