feat(webui): sanctioned restart and graceful reload controls (Closes #642)
Adds a gated `system.restart_namespace` action so operators and workers can restart or gracefully reload MCP namespaces through an authorized, audited path instead of manual host process killing (#630). - webui/sanctioned_restart.py: restart/reload operation model, dry-run intent preview, confirmation-string enforcement, audit emission, and post-restart health verification. Fails closed on unknown auth, missing capability, or ambiguous target namespace. - webui/console_authz.py: RBAC entries for the restart capability with secret redaction preserved. - webui/gated_actions.py: registers the restart action in the gated action framework so it cannot be invoked without capability + confirmation. - task_capability_map.py: capability mapping for the restart operation. - docs/sanctioned-restart-controls.md: operator documentation for the sanctioned path and the explicit prohibition on pkill recovery. - docs/webui-authz-audit.md: audit model updated for restart events. - tests/test_webui_sanctioned_restart.py: authorized preview, unauthorized deny, confirmation enforcement, contamination classification, and audit emission coverage. No unrestricted kill path is exposed; manual pkill remains classified as contamination and continues to block clean claims. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -110,6 +110,8 @@ def _format_target(action_id: str, params: dict[str, Any]) -> str:
|
||||
)
|
||||
if action_id == "create_issue":
|
||||
return f"issue {params.get('title', '?')!r}"
|
||||
if action_id in {"system.restart_namespace", "system.reload_namespace"}:
|
||||
return f"MCP namespace {params.get('namespace', '?')!r}"
|
||||
return "unspecified"
|
||||
|
||||
|
||||
@@ -165,6 +167,17 @@ def build_action_registry() -> ActionRegistry:
|
||||
"gitea_create_issue_comment", "Post a PR review thread comment."),
|
||||
("close_pr", "Close PR", "close_pr", "gitea_edit_pr",
|
||||
"Close a pull request without merge."),
|
||||
# #642: the sanctioned replacement for the forbidden manual daemon-kill
|
||||
# recovery path (#630). The "tool" is a host supervisor hook, not an MCP
|
||||
# call — the console never signals a process. Preview and gating live in
|
||||
# ``webui.sanctioned_restart``; these stay disabled like every other
|
||||
# registry entry.
|
||||
("system.reload_namespace", "Reload MCP namespace", "reload_namespace",
|
||||
"host.supervisor_reload",
|
||||
"Gracefully reload one MCP namespace via the host supervisor."),
|
||||
("system.restart_namespace", "Restart MCP namespace",
|
||||
"restart_namespace", "host.supervisor_restart",
|
||||
"Restart one MCP namespace via the host supervisor."),
|
||||
)
|
||||
actions = tuple(
|
||||
GatedAction(
|
||||
|
||||
Reference in New Issue
Block a user