feat(tickets): open, comment and resolve tickets from the UI and MCP; known issues on alerts and anomalies (#6351, #6352)
A ticket records a known problem: title, area, hosts, subject, description, links to alert rules and anomalies, and a log of comments, edits, resolving and reopening. Opening, resolving (with the resolution as the note) and reopening go on the timeline; every step is audited, and the audit rows stay off the timeline. An alert or anomaly an open ticket covers (by link, by subject, or a host-wide ticket on the host itself) shows a Known issue badge, and offers "Open a ticket" prefilled from its rule or anomaly when none does. Hosts, monitors and containers get a Known issues card. MCP: list_tickets, get_ticket (viewer); open_ticket, update_ticket, comment_ticket, resolve_ticket, reopen_ticket (operator). list_alerts and list_anomalies carry known_issue; get_overview, get_host, get_monitor and get_container carry open_tickets. Also update_change and delete_change for changes recorded by hand (author or admin). Tickets are left out of backups, like the timeline (2026-10-15 ruling). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -79,6 +79,7 @@ Each role includes the tools of the roles above it in this list.
|
||||
| Automation | `list_playbooks`, `get_playbook`, `list_inventory`, `list_ansible_runs`, `get_ansible_run`, `wait_for_run`, `list_schedules` |
|
||||
| Audit | `list_audit_events` |
|
||||
| Timeline | `get_timeline` (everything that happened, newest first, in one list: alerts firing and clearing, playbook runs and what they changed, maintenance, reboots, deploys and crash-loops, reviewed anomalies, changes recorded by hand, changes to Steward's settings; by host, area or kind, and kept after the records it came from age out) |
|
||||
| Tickets | `list_tickets` (known problems someone recorded, open first; by host, subject or area), `get_ticket` (one ticket: its description, the alert rules and anomalies it links, its resolution and its log). `list_alerts` and `list_anomalies` mark what an open ticket covers as `known_issue`; `get_overview`, `get_host`, `get_monitor` and `get_container` carry `open_tickets` |
|
||||
| Docker plugin | `list_containers`, `get_container`, `list_swarm_services`, `search_container_logs`, `get_docker_disk_usage` |
|
||||
| Host agent (built in, always on) | `list_agents`, `list_maintenance_plans` (each plan's group, weekly slot, next run and latest run: how far it got, who it skipped and why), `get_maintenance_job` (a host's update jobs or one job: state, packages updated, whether it rebooted, why it failed), `get_host_senders` (who sent or received a host's network traffic over a window: its containers and NFS mounts ranked at the host's peak, and what none of them accounts for) |
|
||||
| Traefik plugin | `list_routers` (each router graded as the Traefik page grades it, with the status codes behind a problem, what they mean and the swarm service behind it), `get_router` (one router's history, codes, normal and anomalies), `list_certificates` (served certificates, days left, the routers they cover), `get_traffic` (the access log's clients, countries and routers, and Traefik's own figures) |
|
||||
@@ -95,7 +96,8 @@ Each role includes the tools of the roles above it in this list.
|
||||
| Monitors | `create_monitor`, `update_monitor`, `set_monitor_enabled` |
|
||||
| Alerts | `create_alert_rule`, `update_alert_rule`, `acknowledge_alert`, `send_test_alert`, `create_maintenance_window`, `end_maintenance_window` |
|
||||
| Metric anomalies | `review_anomaly` (mark one or several normal, so their hours are learned, or real, with a note saying why) |
|
||||
| Timeline | `log_change` (record a change Steward can't see happen — a config edited by hand, a disk swapped — on the hosts it was done on, under an area; shown as by "<you> via MCP") |
|
||||
| Timeline | `log_change` (record a change Steward can't see happen — a config edited by hand, a disk swapped — on the hosts it was done on, under an area; shown as by "<you> via MCP"), `update_change` and `delete_change` (correct or take back a recorded change: its author or an admin) |
|
||||
| Tickets | `open_ticket` (a known problem, on its hosts and what it is about, linked to alert rules and anomalies; goes on the timeline), `update_ticket` (its fields and links), `comment_ticket`, `resolve_ticket` (with what fixed it, which goes on the timeline), `reopen_ticket` |
|
||||
|
||||
### Admin: destructive and remediation
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Tickets
|
||||
|
||||
A ticket records a known problem that takes more than a moment to fix: a disk failing slowly, a service that keeps restarting, a backup that times out at night. It says what is wrong, what has been tried and, once resolved, what fixed it. Acknowledging an alert only quiets it. A ticket keeps the story, and while it is open the alerts and anomalies it covers read as a **known issue**.
|
||||
|
||||
Open the list from **Tickets** in the sidebar. A host's page, a monitor's page and a container's page each show a **Known issues** card with their open tickets. MCP clients use `list_tickets`, `get_ticket`, `open_ticket`, `update_ticket`, `comment_ticket`, `resolve_ticket` and `reopen_ticket`.
|
||||
|
||||
**Source:** `steward/tickets/` (`service.py` for every write, `routes.py` for the pages), `steward/mcp/tools/tickets.py`
|
||||
**Tables:** `tickets`, `ticket_links`, `ticket_notes` (`steward/models/tickets.py`)
|
||||
|
||||
---
|
||||
|
||||
## What a ticket holds
|
||||
|
||||
| Field | |
|
||||
|---|---|
|
||||
| Number | `#12`, in order of opening |
|
||||
| Title | What is wrong, one line (up to 200 characters) |
|
||||
| Area | One of the timeline's areas (Settings → Timeline) |
|
||||
| Hosts | The hosts it is on, by name; none for a problem not on any one host |
|
||||
| Subject | What it is about when that is not a host as a whole: an alert rule's resource (`web-1:/`), a container (`web-1/nginx`), a monitor's name |
|
||||
| Description | What is seen, since when, what has been tried |
|
||||
| Links | Alert rules and metric anomalies it covers |
|
||||
| Log | Opened, comments, edits (which fields changed, what was linked or unlinked), resolved, reopened, each with who and when |
|
||||
|
||||
A ticket is **open** or **resolved**. Resolving one needs a resolution: what fixed it, or why it no longer matters. Reopening clears it, with an optional reason.
|
||||
|
||||
## Opening one
|
||||
|
||||
- **From an alert:** **Open a ticket** on an active alert (Alerts page) fills in the title, area, hosts and subject from the rule, and links the rule.
|
||||
- **From an anomaly:** **Open a ticket** on an anomaly to review, or one reviewed as real, does the same and links the anomaly.
|
||||
- **From a page:** the **Known issues** card on a host, monitor or container opens one about it.
|
||||
- **From scratch:** **Open a ticket** on the Tickets page.
|
||||
- **Through MCP:** `open_ticket(title, area, description, hosts, subject, alert_rules, anomalies)`.
|
||||
|
||||
Opening, editing, commenting, resolving and reopening need an operator or admin. Viewers read everything.
|
||||
|
||||
## Known issues
|
||||
|
||||
An alert or anomaly is a known issue while an open ticket:
|
||||
|
||||
- links its alert rule (or the covering group rule it came from), or the anomaly;
|
||||
- names its resource as its subject; or
|
||||
- is about its host as a whole (no subject), when the resource is the host itself.
|
||||
|
||||
The Alerts page and the anomaly lists show a **Known issue** badge with the ticket. `list_alerts` and `list_anomalies` return the same as `known_issue`. `get_overview` lists up to ten open tickets in `open_tickets` and counts them all in `counts.tickets_open`. `get_host`, `get_monitor` and `get_container` carry their own `open_tickets`.
|
||||
|
||||
## On the timeline
|
||||
|
||||
Opening, resolving and reopening a ticket each write a timeline entry under the ticket's area, on its hosts and subject (`ticket.opened`, `ticket.resolved`, `ticket.reopened`, kind Ticket). The resolution is the resolved entry's note, so the timeline keeps what fixed it. Comments and edits stay in the ticket's own log.
|
||||
|
||||
Every step is audited (`ticket.opened`, `ticket.edited`, `ticket.commented`, `ticket.resolved`, `ticket.reopened`). Those audit rows stay off the timeline, which holds the ticket's own entries.
|
||||
|
||||
## Backups
|
||||
|
||||
Tickets are not in Steward's backups yet, the same as the timeline itself (`steward/backup/contents.py`, `EXCLUDED`).
|
||||
@@ -23,6 +23,7 @@ Every source writes its entry in the same transaction as its own record, through
|
||||
| A host away: a reboot, a crash, lost contact, the agent restarting; kept up to date until it ends | host agent: `downtime.record_events` | `host.rebooted`, `host.lost_contact`, `host.agent_restarted` |
|
||||
| Container snapshots: a new image, a recreated container, a restart or death | Docker: `ingest.persist_host_docker` → `timeline.from_snapshot` | `docker.deployed`, `docker.crash_loop`, `docker.restarted` |
|
||||
| A change recorded by hand, from the Timeline page, a host's History card or MCP `log_change` | `timeline/changes.py` | `change.recorded` |
|
||||
| Tickets: opened, resolved (the resolution is the entry's note) and reopened | `tickets/service.py` | `ticket.opened`, `ticket.resolved`, `ticket.reopened` |
|
||||
|
||||
A source whose record changes after it is written keeps one entry for it (`record.record_or_update`): a downtime gets its length when it ends, an anomaly changes type when it is reviewed.
|
||||
|
||||
@@ -38,7 +39,7 @@ On its first start, Steward fills the timeline from the last 90 days of records
|
||||
|
||||
Steward can't see every change happen: a config edited on a host, a disk swapped, a service moved. Whoever made it records it, with **Record a change** on the Timeline page or a host's History card, or the `log_change` MCP tool. It says what was done, the area it is about, when, the hosts it was done on (none for a change not on any one host), optionally what it was done to (a container, a file) and a longer detail.
|
||||
|
||||
An operator or admin can record one. Its author can edit or delete it from its **Edit** link, and so can an admin. One recorded through MCP is shown as by "<user> via MCP" and is editable by that token's user. Recording, editing and deleting are audited (`timeline.change_recorded`, `…_edited`, `…_deleted`), and those audit rows stay off the timeline, which holds the change itself.
|
||||
An operator or admin can record one. Its author can edit or delete it from its **Edit** link, or with the `update_change` and `delete_change` MCP tools, and so can an admin. One recorded through MCP is shown as by "<user> via MCP" and is editable by that token's user. Recording, editing and deleting are audited (`timeline.change_recorded`, `…_edited`, `…_deleted`), and those audit rows stay off the timeline, which holds the change itself.
|
||||
|
||||
## Areas and kinds
|
||||
|
||||
|
||||
Reference in New Issue
Block a user