Files
FabledSteward/docs/core/timeline.md
T
bvandeusenandClaude Opus 5.5 4ea735d87c
CI / lint (push) Successful in 2s
CI / unit (push) Failing after 1m31s
CI / integration (push) Successful in 1m41s
CI / publish (push) Skipped
feat(timeline): one record of what happened to the infrastructure, newest first (#6348)
A stored log, timeline_entries, with one row per thing that happened. Each
source writes its row in the same transaction as its own record, so the
history outlives the 30/90/365-day retention of the tables it points to:

- alert events (core/alerts._add_event): fired, resolved, acknowledged,
  went silent, readings back;
- finished Ansible runs (executor, and the startup sweep of interrupted
  runs): changed something, changed nothing, failed, dry run;
- audit rows (core/audit.log_audit): Steward's settings, users and access,
  sign-ins, backups. Actions another writer records are left to it.

Entries carry a kind (fixed) and an area (Service, Host OS, Hardware,
Storage, Network, Security, Monitoring). Settings → Timeline edits the
areas and each type's area and visibility; both are applied when the
timeline is read, so a change re-sorts past entries and a hidden type
comes back when shown. Kept forever unless a retention is set.

The first start fills the timeline from the last 90 days of records once,
skipping any already covered. A host rename carries its entries.

UI: Timeline in the sidebar (filter by host, area, kind; scrolls on), a
History card on each host page. MCP: get_timeline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:03:05 -04:00

3.2 KiB

Timeline

The timeline is one list of everything that happened to the infrastructure, newest first: alerts firing and clearing, playbook runs and what they changed, and changes to Steward's own settings. Open it from Timeline in the sidebar, from a host's History card, or through the get_timeline MCP tool.

Each entry stands on its own. An alert event, an Ansible run or an audit row is pruned after its own retention, but the entry for it stays. Its link then reads "Details no longer kept".

Source: steward/timeline/ Table: timeline_entries (steward/models/timeline.py)


What is recorded

Every source writes its entry in the same transaction as its own record, through steward/timeline/record.py:

Source Written by Entry types
Alert events core/alerts._add_event alert.fired, alert.resolved, alert.acknowledged, alert.silent, alert.resumed
Ansible runs, when they finish ansible/executor.start_run, and the startup sweep of interrupted runs ansible.changed, ansible.unchanged, ansible.failed, ansible.check
Audit rows core/audit.log_audit steward.config, steward.access, steward.signin, steward.backup

Some audit actions are left to another writer, because the event itself is recorded there: starting a run, acknowledging an alert, and starting maintenance. A person's own preferences (time zone, email settings) are not recorded. catalog.audit_type holds that mapping.

On its first start, Steward fills the timeline from the last 90 days of alert events, finished runs and audit rows (service.backfill). It does this once, and records when in timeline.backfilled_at. An entry for a record that is already covered is skipped.

Areas and kinds

An entry has a kind, which says how it came about: Change, Automation, Alert, Anomaly, Ticket or Steward. Kinds are fixed in catalog.KINDS.

An entry also has an area, which says what part of the infrastructure it is about. The areas start as Service, Host OS, Hardware, Storage, Network, Security and Monitoring.

Each entry is written with an area of its own. An alert's area comes from what its rule watches (catalog.area_of_metric): drive health is Hardware, a mount is Storage, an HTTP monitor is Service. Other entries use their type's area.

Settings → Timeline

Setting Key Default
Areas, in order: rename, add, or remove one and move its entries to another timeline.areas, timeline.area_moved the seven above
Each type's area ("As recorded" keeps each entry's own) and whether it is shown timeline.types from the catalog
Keep for, in days (0 keeps everything) timeline.retention_days 0

These are applied when the timeline is read, never written onto an entry. A change re-sorts the whole history. A hidden type is still recorded, so showing it again brings its entries back.

Hidden by default: dry runs, playbook runs that changed nothing, and sign-ins.

Adding a type

A plugin registers its types in setup() with catalog.register_type(key, label, kind, area, shown, about), and writes entries with record.record(session, type=…, at=…, summary=…, hosts=…, ref=(kind, id)). Settings → Timeline lists the new type at once.