feat: Claude Code Monitor — lanes, pipelines and a merged workspace

Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
This commit is contained in:
2026-07-29 17:07:45 +07:00
commit 57dc91585d
783 changed files with 221743 additions and 0 deletions
@@ -0,0 +1,835 @@
/**
* @file Enriched OpenAPI OVERRIDE operations for the Pricing and Alerts routes.
*
* These eight paths are ALREADY documented in `server/openapi.js`. This module
* re-declares the SAME operations (identical operationId / tags / request &
* response `$ref` schema names / parameters) but layers on richer prose
* descriptions plus realistic request/response/parameter examples so the
* generated Swagger UI is self-explanatory. The wire contract is unchanged —
* no new schemas, no new tags. The base `$ref`s under
* `#/components/{schemas,parameters}` are reused verbatim.
*
* Shape: `{ tags: [], schemas: {}, paths: { ... } }`. The `tags` and `schemas`
* collections are intentionally empty; everything here is a path-level override.
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
// ---------------------------------------------------------------------------
// Reusable realistic examples (kept here, NOT as components — examples live
// inline on the operations so the override carries no schema/component state).
// ---------------------------------------------------------------------------
/** A representative stored pricing rule (matches PricingRule schema fields). */
const PRICING_RULE_EXAMPLE = {
model_pattern: "claude-opus-4%",
display_name: "Claude Opus 4 (family)",
input_per_mtok: 15,
output_per_mtok: 75,
cache_read_per_mtok: 1.5,
cache_write_per_mtok: 18.75,
cache_write_1h_per_mtok: 30,
fast_input_per_mtok: 0,
fast_output_per_mtok: 0,
updated_at: "2026-06-25T18:42:11.000Z",
};
/** A second rule to make the list example look like a real catalog. */
const PRICING_RULE_EXAMPLE_2 = {
model_pattern: "claude-haiku%",
display_name: "Claude Haiku (family)",
input_per_mtok: 0.8,
output_per_mtok: 4,
cache_read_per_mtok: 0.08,
cache_write_per_mtok: 1,
cache_write_1h_per_mtok: 1.6,
fast_input_per_mtok: 0,
fast_output_per_mtok: 0,
updated_at: "2026-06-20T09:15:00.000Z",
};
/** A full CostResult-shaped example body returned by both cost endpoints. */
const COST_RESULT_EXAMPLE = {
total_cost: 12.8431,
breakdown: [
{
model: "claude-opus-4-8",
speed: "standard",
inference_geo: "global",
service_tier: "standard",
input_tokens: 184320,
output_tokens: 51200,
cache_read_tokens: 920000,
cache_write_tokens: 64000,
cache_write_1h_tokens: 12000,
web_search_requests: 8,
web_fetch_requests: 3,
code_execution_requests: 2,
cost: 8.4127,
matched_rule: "claude-opus-4%",
},
{
model: "claude-haiku-4-5",
speed: "fast",
inference_geo: "us",
service_tier: "standard",
input_tokens: 512000,
output_tokens: 128000,
cache_read_tokens: 64000,
cache_write_tokens: 8000,
cache_write_1h_tokens: 0,
web_search_requests: 0,
web_fetch_requests: 0,
code_execution_requests: 0,
cost: 1.5904,
matched_rule: "claude-haiku%",
},
],
feature_costs: {
web_search_cost: 0.08,
web_fetch_cost: 0,
code_execution_cost: 0,
code_execution_hours_estimated: 0.1667,
code_execution_free_hours: 50,
},
unpriced_models: [
{
model: "claude-experimental-preview",
input_tokens: 4096,
output_tokens: 2048,
cache_read_tokens: 0,
cache_write_tokens: 0,
},
],
daily_costs: [
{ date: "2026-06-23", cost: 3.1102 },
{ date: "2026-06-24", cost: 5.7421 },
{ date: "2026-06-25", cost: 3.9908 },
],
};
/** A single fired-alert event row. `details` is a JSON STRING, per the route. */
const ALERT_EVENT_EXAMPLE = {
id: 42,
rule_id: "7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91",
rule_name: "Idle session watchdog",
rule_type: "inactivity",
message: "Session sess_8f2a has been inactive for 35 minutes",
details: '{"session_id":"sess_8f2a","minutes":35,"threshold":30}',
acknowledged: 0,
created_at: "2026-06-25T17:05:44.000Z",
};
/** A serialized alert RULE (config parsed to an object, enabled coerced bool). */
const ALERT_RULE_EXAMPLE = {
id: "7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91",
name: "Idle session watchdog",
rule_type: "inactivity",
config: { minutes: 30 },
enabled: true,
cooldown_seconds: 300,
created_at: "2026-06-10T12:00:00.000Z",
updated_at: "2026-06-24T08:30:00.000Z",
};
/** A second rule of a different type for the list example. */
const ALERT_RULE_EXAMPLE_2 = {
id: "1a2b3c4d-5e6f-7081-9201-aabbccddeeff",
name: "Heavy token burn",
rule_type: "token_threshold",
config: { total_tokens: 5000000 },
enabled: true,
cooldown_seconds: 600,
created_at: "2026-06-12T14:20:00.000Z",
updated_at: "2026-06-12T14:20:00.000Z",
};
module.exports = {
// No new tags — reuse the base "Pricing" and "Alerts" tags.
tags: [],
// No new schemas — every $ref below points at the base components.
schemas: {},
paths: {
// -----------------------------------------------------------------------
// PRICING
// -----------------------------------------------------------------------
"/api/pricing": {
get: {
tags: ["Pricing"],
summary: "List pricing rules",
operationId: "listPricingRules",
description:
"Returns every stored pricing rule, wrapped as `{ pricing: [ ... ] }`. " +
"Each rule carries per-MTok (per-million-token) rates for input, output, " +
"cache reads, and the two cache-write tiers (5-minute and 1-hour " +
"ephemeral), plus optional fast-mode input/output rates (0 = not " +
"configured). Rules are matched against model ids by treating the SQL " +
"`%` wildcard in `model_pattern` as `.*`; when several rules match, the " +
"longest (most specific) pattern wins. Rates here feed the cost " +
"calculations under `/api/pricing/cost`.",
responses: {
200: {
description: "Pricing rules",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/PricingListResponse" },
example: { pricing: [PRICING_RULE_EXAMPLE, PRICING_RULE_EXAMPLE_2] },
},
},
},
},
},
put: {
tags: ["Pricing"],
summary: "Create/update pricing rule",
operationId: "upsertPricingRule",
description:
"Creates a pricing rule or updates the existing one with the same " +
"`model_pattern` (upsert keyed on `model_pattern`). `model_pattern` and " +
"`display_name` are required; every `*_per_mtok` rate is optional and " +
"defaults to 0 when omitted. Use the SQL `%` wildcard in `model_pattern` " +
"to match a model family (e.g. `claude-opus-4%`). Set " +
"`fast_input_per_mtok` / `fast_output_per_mtok` only if the model bills " +
"fast-mode usage at a premium; leave them 0 otherwise. " +
"Note the asymmetry with the list endpoint: the response wraps a SINGLE " +
"stored rule as `{ pricing: <rule> }` (not an array). A missing " +
"`model_pattern` or `display_name` returns 400 `INVALID_INPUT`, and so " +
"does any `*_per_mtok` rate that is not a non-negative finite number " +
"(numeric strings are coerced; NaN and negative rates are rejected " +
"before anything is written).",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/PricingUpsertRequest" },
example: {
model_pattern: "claude-opus-4%",
display_name: "Claude Opus 4 (family)",
input_per_mtok: 15,
output_per_mtok: 75,
cache_read_per_mtok: 1.5,
cache_write_per_mtok: 18.75,
cache_write_1h_per_mtok: 30,
fast_input_per_mtok: 0,
fast_output_per_mtok: 0,
},
},
},
},
responses: {
200: {
description: "Pricing rule stored",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/PricingUpsertResponse" },
example: { pricing: PRICING_RULE_EXAMPLE },
},
},
},
400: {
description: "Invalid request body",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: {
code: "INVALID_INPUT",
message: "model_pattern and display_name are required",
},
},
},
},
},
},
},
},
"/api/pricing/{pattern}": {
delete: {
tags: ["Pricing"],
summary: "Delete pricing rule",
operationId: "deletePricingRule",
description:
"Deletes the pricing rule whose `model_pattern` exactly matches the " +
"`pattern` path segment. The pattern is URL-ENCODED: the SQL `%` " +
"wildcard must be sent as `%25` (so `claude-opus-4%` becomes " +
"`claude-opus-4%25`). The server decodes it before lookup. Returns " +
"`{ ok: true }` on success, or 404 `NOT_FOUND` if no rule matches.",
parameters: [
// Mirrors components.parameters.PatternPath (name/in/required/schema
// identical), inlined so a realistic URL-encoded example can be
// attached — a bare $ref cannot carry an `example`.
{
name: "pattern",
in: "path",
required: true,
schema: { type: "string" },
description:
"Model pattern (URL-encoded). The SQL `%` wildcard must be escaped " +
"as `%25` (e.g. `claude-opus-4%25` for the rule `claude-opus-4%`).",
example: "claude-opus-4%25",
},
],
responses: {
200: {
description: "Rule deleted",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/DeleteOkResponse" },
example: { ok: true },
},
},
},
404: {
description: "Pricing rule not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: { code: "NOT_FOUND", message: "Pricing rule not found" },
},
},
},
},
},
},
},
"/api/pricing/cost": {
get: {
tags: ["Pricing"],
summary: "Get total token cost across all sessions",
operationId: "getTotalCost",
description:
"Computes the aggregate token cost across EVERY session by matching " +
"each (model, speed, inference_geo, service_tier) usage bucket against " +
"the most specific pricing rule. Returns `total_cost`, a per-bucket " +
"`breakdown`, `feature_costs` (web-search surcharge, code-execution " +
"container time with the org free-hours allowance applied), " +
"`unpriced_models` (usage with no matching rule, contributing $0 so the " +
"total stays honest), and `daily_costs` bucketed by local calendar day. " +
"Pass `tz_offset` (minutes; the JS `Date.getTimezoneOffset()` value, " +
"e.g. 300 for US Eastern, -120 for CEST) so day boundaries align with " +
"the viewer's timezone; omitted/invalid offsets fall back to UTC. " +
"Honors the `sources` data-scope filter, like the sessions / stats / " +
"analytics endpoints, so the reported cost matches the active scope.",
parameters: [
{
name: "tz_offset",
in: "query",
required: false,
schema: { type: "integer" },
description:
"Viewer timezone offset in minutes, as returned by " +
"`Date.getTimezoneOffset()` (positive for zones behind UTC, e.g. " +
"300 = US Eastern, -120 = CEST). Shifts the `daily_costs` day " +
"boundaries; invalid or omitted values default to UTC.",
example: 300,
},
{
name: "sources",
in: "query",
required: false,
schema: { type: "string" },
description:
"Comma-separated data-source ids to include (local history is " +
"`local`; remote SSH machines use their `remote_sources.id`). Omit " +
"for all sources. Narrows the aggregate cost to the given origins.",
example: "local",
},
],
responses: {
200: {
description: "Cost result",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/CostResult" },
example: COST_RESULT_EXAMPLE,
},
},
},
},
},
},
"/api/pricing/cost/{sessionId}": {
get: {
tags: ["Pricing"],
summary: "Get token cost for one session",
operationId: "getSessionCost",
description:
"Same cost computation as `/api/pricing/cost`, but scoped to a single " +
"session's token usage. Returns the identical `CostResult` shape " +
"(`total_cost`, `breakdown`, `feature_costs`, `unpriced_models`, " +
"`daily_costs`); `daily_costs` holds at most one entry — the session's " +
"start date in the viewer's local day, or an empty array if the session " +
"id is unknown. Pass `tz_offset` (minutes, `Date.getTimezoneOffset()`) " +
"to place that start date in the viewer's timezone; defaults to UTC.",
parameters: [
{
name: "sessionId",
in: "path",
required: true,
schema: { type: "string" },
description: "Session ID to price.",
example: "sess_8f2a3b1c",
},
{
name: "tz_offset",
in: "query",
required: false,
schema: { type: "integer" },
description:
"Viewer timezone offset in minutes (`Date.getTimezoneOffset()`; " +
"300 = US Eastern, -120 = CEST). Places the session start date in " +
"the viewer's local day; defaults to UTC when omitted or invalid.",
example: 300,
},
],
responses: {
200: {
description: "Session cost result",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/CostResult" },
example: {
...COST_RESULT_EXAMPLE,
total_cost: 8.4127,
daily_costs: [{ date: "2026-06-25", cost: 8.4127 }],
},
},
},
},
},
},
},
// -----------------------------------------------------------------------
// ALERTS
// -----------------------------------------------------------------------
"/api/alerts": {
get: {
tags: ["Alerts"],
summary: "List fired alerts, newest first",
operationId: "listAlerts",
description:
"Returns the fired-alert feed, newest first, as " +
"`{ alerts, total, unacked, limit, offset }`. Each alert event carries " +
"the originating rule's id/name/type, a human-readable `message`, an " +
"`acknowledged` flag (0/1), `created_at`, and `details` — which is a " +
"JSON STRING (not an object) that callers must `JSON.parse`. " +
"`limit` is clamped to 1200 (default 50) and negative `offset` is " +
"clamped to 0. Set `unacked=true` to return only unacknowledged alerts; " +
"`total` then counts only unacked rows, while `unacked` always reports " +
"the global unacknowledged count.",
parameters: [
{
name: "limit",
in: "query",
required: false,
schema: { type: "integer", minimum: 1, maximum: 200, default: 50 },
description:
"Page size, clamped to the 1200 range (default 50). Values " +
"outside the range are clamped, not rejected.",
example: 50,
},
{
name: "offset",
in: "query",
required: false,
schema: { type: "integer", minimum: 0 },
description: "Pagination offset; negative values are clamped to 0.",
example: 0,
},
{
name: "unacked",
in: "query",
required: false,
schema: { type: "boolean" },
description:
'When the literal string "true", return only unacknowledged ' +
"alerts (and scope `total` to that subset).",
example: "true",
},
],
responses: {
200: {
description: "Paginated alert feed with total and unacked counts",
content: {
"application/json": {
schema: {
type: "object",
additionalProperties: true,
description: "Includes alerts[], total, unacked, limit, offset.",
},
example: {
alerts: [
ALERT_EVENT_EXAMPLE,
{
id: 41,
rule_id: "1a2b3c4d-5e6f-7081-9201-aabbccddeeff",
rule_name: "Heavy token burn",
rule_type: "token_threshold",
message: "Session sess_3c1d crossed 5,000,000 total tokens",
details:
'{"session_id":"sess_3c1d","total_tokens":5120000,"threshold":5000000}',
acknowledged: 1,
created_at: "2026-06-25T16:40:02.000Z",
},
],
total: 2,
unacked: 1,
limit: 50,
offset: 0,
},
},
},
},
},
},
},
"/api/alerts/rules": {
get: {
tags: ["Alerts"],
summary: "List alert rules",
operationId: "listAlertRules",
description:
"Returns all alert rules as `{ rules: [ ... ] }`. Each rule's `config` " +
"is returned as a PARSED object (the column is stored as JSON text), and " +
"`enabled` is coerced to a boolean. The `config` shape depends on " +
"`rule_type`: `event_pattern` uses event_type / tool_name / " +
"summary_contains plus optional count + window_minutes; `inactivity` " +
"uses `minutes`; `status_duration` uses `status` + `minutes`; " +
"`token_threshold` uses `total_tokens`.",
responses: {
200: {
description: "All alert rules with parsed config objects",
content: {
"application/json": {
schema: { type: "object", additionalProperties: true },
example: { rules: [ALERT_RULE_EXAMPLE, ALERT_RULE_EXAMPLE_2] },
},
},
},
},
},
post: {
tags: ["Alerts"],
summary: "Create an alert rule",
operationId: "createAlertRule",
description:
"Creates an alert rule and returns it serialized as `{ rule: { ... } }` " +
"with HTTP 201. `name`, `rule_type`, and `config` are required; the " +
"`config` shape is validated per `rule_type`:\n" +
"- `event_pattern`: `{ event_type?, tool_name?, summary_contains?, " +
"count?, window_minutes? }` — fires when matching events accumulate.\n" +
"- `inactivity`: `{ minutes }` — fires when a session goes idle.\n" +
"- `status_duration`: `{ status, minutes }` — fires when a session " +
"holds a status too long.\n" +
"- `token_threshold`: `{ total_tokens }` — fires when usage crosses a " +
"ceiling.\n" +
"`enabled` defaults to true and `cooldown_seconds` defaults to 300 " +
"(must be a non-negative integer). A bad name, unknown `rule_type`, " +
"invalid `config`, or negative cooldown returns 400 `INVALID_INPUT`.",
requestBody: {
required: true,
content: {
"application/json": {
schema: {
type: "object",
required: ["name", "rule_type", "config"],
properties: {
name: { type: "string" },
rule_type: {
type: "string",
enum: ["event_pattern", "inactivity", "status_duration", "token_threshold"],
},
config: {
type: "object",
additionalProperties: true,
description:
"Type-specific config. event_pattern: event_type/tool_name/summary_contains + optional count/window_minutes. inactivity: minutes. status_duration: status + minutes. token_threshold: total_tokens.",
},
enabled: { type: "boolean", default: true },
cooldown_seconds: { type: "integer", default: 300 },
},
},
examples: {
inactivity: {
summary: "Inactivity rule",
value: {
name: "Idle session watchdog",
rule_type: "inactivity",
config: { minutes: 30 },
enabled: true,
cooldown_seconds: 300,
},
},
event_pattern: {
summary: "Event-pattern rule (repeated tool errors)",
value: {
name: "Repeated Bash failures",
rule_type: "event_pattern",
config: {
event_type: "PostToolUse",
tool_name: "Bash",
summary_contains: "error",
count: 3,
window_minutes: 10,
},
enabled: true,
cooldown_seconds: 600,
},
},
status_duration: {
summary: "Status-duration rule",
value: {
name: "Stuck waiting too long",
rule_type: "status_duration",
config: { status: "waiting", minutes: 15 },
enabled: true,
cooldown_seconds: 300,
},
},
token_threshold: {
summary: "Token-threshold rule",
value: {
name: "Heavy token burn",
rule_type: "token_threshold",
config: { total_tokens: 5000000 },
enabled: true,
cooldown_seconds: 600,
},
},
},
},
},
},
responses: {
201: {
description: "Created rule",
content: {
"application/json": {
schema: { type: "object", additionalProperties: true },
example: { rule: ALERT_RULE_EXAMPLE },
},
},
},
400: {
description: "Validation error",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: { code: "INVALID_INPUT", message: "name is required" },
},
},
},
},
},
},
},
"/api/alerts/rules/{id}": {
patch: {
tags: ["Alerts"],
summary: "Update an alert rule (partial; rule_type is immutable)",
operationId: "updateAlertRule",
description:
"Partially updates an alert rule and returns it serialized as " +
"`{ rule: { ... } }`. Only the fields present in the body change; " +
"`rule_type` CANNOT be changed and any supplied `config` is validated " +
"against the rule's STORED type. `name` (if present) must be a " +
"non-empty string and `cooldown_seconds` (if present) must be a " +
"non-negative integer. Returns 404 `NOT_FOUND` for an unknown id, or " +
"400 `INVALID_INPUT` for a bad name, invalid config, or negative " +
"cooldown.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
description: "Alert rule ID (UUID).",
example: "7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91",
},
],
requestBody: {
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
name: { type: "string" },
config: { type: "object", additionalProperties: true },
enabled: { type: "boolean" },
cooldown_seconds: { type: "integer" },
},
},
examples: {
disableRule: {
summary: "Disable a rule without touching its config",
value: { enabled: false },
},
retuneInactivity: {
summary: "Re-tune an inactivity rule's threshold + cooldown",
value: { config: { minutes: 45 }, cooldown_seconds: 900 },
},
rename: {
summary: "Rename a rule",
value: { name: "Idle session watchdog (prod)" },
},
},
},
},
},
responses: {
200: {
description: "Updated rule",
content: {
"application/json": {
schema: { type: "object", additionalProperties: true },
example: {
rule: { ...ALERT_RULE_EXAMPLE, enabled: false, cooldown_seconds: 900 },
},
},
},
},
400: {
description: "Validation error",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: {
code: "INVALID_INPUT",
message: "cooldown_seconds must be a non-negative integer",
},
},
},
},
},
404: {
description: "Rule not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: { code: "NOT_FOUND", message: "Alert rule not found" },
},
},
},
},
},
},
delete: {
tags: ["Alerts"],
summary: "Delete an alert rule and its fired-alert history",
operationId: "deleteAlertRule",
description:
"Deletes the alert rule with the given id. Its fired-alert history " +
"cascades away with it (the foreign key is ON DELETE CASCADE), so any " +
"alerts previously raised by this rule are also removed from the feed. " +
"Returns `{ ok: true }` on success or 404 `NOT_FOUND` for an unknown id.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string" },
description: "Alert rule ID (UUID).",
example: "7c1d8e2a-9b34-4f50-a1c2-6d8e0f3b5a91",
},
],
responses: {
200: {
description: "Deletion confirmation",
content: {
"application/json": {
schema: { type: "object", additionalProperties: true },
example: { ok: true },
},
},
},
404: {
description: "Rule not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: { code: "NOT_FOUND", message: "Alert rule not found" },
},
},
},
},
},
},
},
"/api/alerts/{id}/ack": {
post: {
tags: ["Alerts"],
summary: "Acknowledge one fired alert",
operationId: "ackAlert",
description:
"Marks a single fired alert (by its integer event id) as acknowledged " +
"and returns the updated row as `{ alert: { ... } }` (with " +
"`acknowledged: 1`). Acknowledging also broadcasts an `alert_updated` " +
"WebSocket message so connected dashboards refresh their unacked badge. " +
"The id must be numeric; an unknown id returns 404 `NOT_FOUND`.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "integer" },
description: "Alert event ID (numeric).",
example: 42,
},
],
responses: {
200: {
description: "Acknowledged alert row",
content: {
"application/json": {
schema: { type: "object", additionalProperties: true },
example: { alert: { ...ALERT_EVENT_EXAMPLE, acknowledged: 1 } },
},
},
},
404: {
description: "Alert not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/ErrorResponse" },
example: {
error: { code: "NOT_FOUND", message: "Alert not found" },
},
},
},
},
},
},
},
"/api/alerts/ack-all": {
post: {
tags: ["Alerts"],
summary: "Acknowledge all unacked alerts",
operationId: "ackAllAlerts",
description:
"Acknowledges every currently unacknowledged alert in one call and " +
"returns `{ ok: true, acknowledged: <count> }` where `acknowledged` is " +
"the number of rows actually updated. When at least one alert is " +
"acknowledged, an `alert_updated` WebSocket message (`{ acked_all: " +
"true }`) is broadcast so dashboards clear their unacked badge. Calling " +
"this when nothing is unacked returns `acknowledged: 0`.",
responses: {
200: {
description: "Count of acknowledged alerts",
content: {
"application/json": {
schema: { type: "object", additionalProperties: true },
example: { ok: true, acknowledged: 3 },
},
},
},
},
},
},
},
};