Commit Graph
7 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5.5 8feec4dc77 feat(mcp): Steward's own backups: the runs, the next one, checking and making one (#5836)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m25s
CI / integration (push) Successful in 1m31s
CI / publish (push) Successful in 1m13s
list_backups, check_backup and make_backup are Settings → Backups for an
agent, admin only like the page. make_backup runs the page's job in the
background and is audited as "<user> via MCP". Restoring stays in the
browser. get_overview names a scheduled backup that failed, is two hours
past its slot, or has no bucket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 13:09:12 -04:00
bvandeusenandClaude Opus 5.5 3ad8eed83d feat: the host agent is always on; the MCP sees and starts host maintenance (#5834)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m32s
CI / unit (push) Successful in 1m39s
CI / publish (push) Successful in 1m14s
#5834 "The host agent is always on, and the MCP sees a host's
maintenance", step 1 of milestone 520 "The MCP sees what Steward has
grown".

The host agent is a core system (operator, 2026-10-15: "it's very core
to the functionality of Steward as a whole and shouldn't be considered
something that can be toggled off"). ALWAYS_ON_PLUGINS in core settings
makes to_plugins_cfg report it enabled whatever its stored row says, so
it always loads and its MCP tools are always listed. Settings -> Plugins
shows it as "Built in: always on" with no switch, and a save keeps it
enabled. Its code and URLs stay under plugins/host_agent: every
installed agent posts to /plugins/host_agent/ingest.

Maintenance through the MCP:
- list_maintenance_plans: each plan's group, weekly slot, reboot policy,
  next slot, and its latest run (hosts, how far it got, who it skipped
  and why, how it ended).
- get_maintenance_job(host | job): a host's jobs, newest and running
  first, or one job: state, packages updated, reboot, drained by, why it
  failed, when its current state times out.
- run_maintenance(host, reboot="if_required" | "never"): admin, as the
  host page's Update now; refuses a busy host or one with no helper;
  audited "<user> via MCP".
- The overview names a host whose latest job in the last week failed or
  timed out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:59:42 -04:00
bvandeusenandClaude Opus 5.5 e122860cdc feat(mcp): write, test and delete this instance's own playbooks (#5748)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m29s
CI / unit (push) Successful in 1m39s
CI / publish (push) Successful in 1m6s
#5748 "MCP can write, edit and test this instance's own playbooks
(steward-local)": two admin tools beside run_playbook, so an agent can
build automation for these hosts that isn't part of Steward itself.

- save_playbook(playbook, content, source="steward-local", replace=false)
  goes through the editor's own validate_playbook_yaml and
  write_playbook. A playbook that isn't a list of plays comes back with
  the YAML error and its line, and nothing is written. An existing file
  is overwritten only with replace=true. steward-builtin is refused
  (Steward's playbooks change in its repository), as are git sources
  (their repo owns them).
- After writing, `ansible-playbook --syntax-check` runs against a
  throwaway localhost inventory (no host contacted, 60 s), so a module
  name Ansible can't resolve shows before any run. The result rides the
  reply; the file is kept either way.
- delete_playbook(playbook, source="steward-local").
- Both audit as ansible.playbook.created/updated/deleted, "<user> via
  MCP", with the size and never the content.

The testing loop is the existing one: run_playbook defaults to check
mode, on one target, then get_ansible_run. The integration test does
that loop with the real ansible-playbook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 12:01:39 -04:00
bvandeusenandClaude Opus 5.5 5569f9934e feat(anomalies): a metric out of its normal is an anomaly to review (#5740)
CI / lint (push) Successful in 2s
CI / integration (push) Failing after 1m24s
CI / unit (push) Successful in 1m37s
CI / publish (push) Skipped
#5740 "A metric that departs from its normal is recorded as an anomaly to
review, and is findable after it ends", milestone 513.

- Every minute each learned key's last 15 minutes is judged against its
  hour's normal: above the usual hold x1.25 and by the metric's floor opens
  an occurrence, which ends when the level comes back.
- metric_anomalies holds one row per item to review; a repeat while it
  still waits joins it as an occurrence (metric_anomaly_spans).
- An ended row nobody reviews within anomalies.review_days (14, Settings ->
  Thresholds) lapses; an ongoing one never does.
- Learning skips every hour an occurrence covers unless its anomaly was
  marked normal: no normal is learned without review.
- Alerts -> Anomalies lists those to review first, then the reviewed and
  lapsed under day dividers; marking is operator/admin and audited.
- The Overview widget and get_overview warn of open ones and count those
  to review; the host page shows its own; the weekly report lists the
  week's and the lapsed; MCP list_anomalies and review_anomaly.
- Reviewed and lapsed rows are pruned at data.retention_days, and kept
  in backups like alert history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:41:57 -04:00
bvandeusenandClaude Opus 5.5 57e040f1d0 feat(mcp): update_group_members puts hosts into an inventory group or takes them out (#5487)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m9s
CI / unit (push) Successful in 1m29s
CI / publish (push) Successful in 1m19s
#5487 "MCP: edit inventory group membership". Group membership decides which
hosts a group alert rule covers, and the MCP could only read it. The new
operator tool takes a group and lists to add and remove, each a target's
name or id or the Steward host it is linked to; an unknown name refuses the
whole change. It answers with the group's targets after and the alert rules
covering the group (whose per-host rules follow within a minute), and audits
as ansible.group.updated by "<user> via MCP", as the Inventory page does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:48:08 -04:00
bvandeusenandClaude Opus 5.5 5ef18c992c feat(alerts): Send test on a rule checks that its alerts reach someone (#5466)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m9s
CI / unit (push) Successful in 1m36s
CI / publish (push) Successful in 1m6s
#5466 "Send test: check a rule's delivery from the rule". A rule's page gets
a Send test button (operator role), and MCP gets send_test_alert. Each sends
a TEST alert for that rule now, by email to everyone who gets alerts and by
webhook. The alert carries the rule's name and condition, at the latest
reading of what it watches; a rule covering hosts uses its first host. The
result for each channel shows under the button, or "Nothing is set up to
send to". Nothing fires, no event is written, and the test is audited as
alert_rule.tested. An alert with no reading now says "has no reading yet"
instead of "is None".

Docs: alerting.md also gains the burst email from #5465 "One email per burst
of alerts".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:33:32 -04:00
bvandeusenandClaude Opus 5.5 3474cdff46 feat(mcp): connect card, MCP docs, and an end-to-end roles/audit test over HTTP
CI / lint (push) Successful in 1s
CI / integration (push) Successful in 44s
CI / unit (push) Successful in 1m5s
CI / publish (push) Successful in 1m15s
MCP-8 #4972 "Connect card, docs, and end-to-end integration test".

- AI access page: a "Connect a client" card with the /mcp URL (from
  Settings → General → Public base URL, else the request host) and recipes
  for Claude Code, Claude Desktop (mcp-remote) and other Streamable HTTP
  clients, plus the reverse-proxy note. The token-created page shows the
  same recipes with the new token filled in, ready to paste.
- docs/core/mcp.md: connecting, the reverse-proxy requirements, the
  security model (token ceiling, default deny, check-mode default, secrets,
  audit), tools by role, response conventions, adding tools.
  docs/architecture.md describes the /mcp dispatch outside the blueprint
  pipeline; docs/reference/code-map.md gains an MCP section.
- tests/integration/test_mcp.py: over HTTP, a viewer token lists no write
  tools and is refused one by name; an operator token reads get_overview
  and its maintenance-window create/end land in the audit log as
  "<user> via MCP" with the token id. (Revoked → 401 was already covered.)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 08:25:41 -04:00