diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 9276fd077..c81b2154f 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -1990,8 +1990,19 @@ only workload telemetry, e.g. Docker-only or Kubernetes-only agents) are now appended as diagnostics-only targets, honoring the scoped-agent filter, so a critical workload-only agent can no longer vanish from the fleet view. Diagnostics-only rows render the diagnostic's status, reasons, and evidence -but offer no host-local command (there is no ledger connection to derive one -from). Page presentation follows the same honesty rule: the +but offer no host-local update command (there is no ledger connection to +derive an update from). Removed rows are the deliberate exception in the +uninstall direction: a removed agent's one remaining next step is host-side +cleanup, so a removed target resolves its command platform from the retained +diagnostic identity through the strict platform resolver and its row +expansion hands off the host-local uninstall command +(`getInfrastructureAgentDoctorUninstallHandoff`) โ€” the single matching +platform command when the identity resolves, or both explicitly labeled +Linux-family and Windows commands when it does not, never one guessed +executable. The handoff carries the diagnostic's agent id and hostname as +uninstall identity flags, and its copy states the command runs on the +affected host itself, not from Pulse. Page presentation follows the same +honesty rule: the "Target" column renders a version only when one is actually published, the host-local command explainer renders only when at least one row offers a command, and the summary chips list only non-zero status counts. The summary @@ -2001,8 +2012,8 @@ also offers plain-text diagnostic reports (fleet-level over the currently visible rows and per-agent from a row expansion) via `formatInfrastructureAgentDoctorReport`; reports carry status, versions, last-seen, reasons, identity evidence, and non-command repair actions, and -must never embed host-local update commands because those can carry install -tokens. +must never embed host-local update or uninstall commands because those can +carry install tokens. ### Governed action readiness remains outside agent lifecycle authority diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index a2d04b6d8..6f9c5b172 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -5972,6 +5972,15 @@ canonical identity when inventory already has it: shell uninstall payloads must carry `--agent-id`, and PowerShell uninstall payloads must carry `PULSE_AGENT_ID`, so deregistration targets the intended governed agent record instead of depending on local fallback files or hostname lookup. +That uninstall identity seam is deliberately narrower than the unified +inventory row: the governed uninstall builders in +`frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx` +accept a caller-supplied identity view carrying only +`agentActionId`/`agentId`/`hostname` (the exported `AgentUninstallIdentity` +pick), so lifecycle surfaces without a ledger-backed inventory row โ€” Agent +Doctor's removed-agent diagnostics handoff โ€” bind copied uninstall payloads to +the retained diagnostic identity through the same governed builders instead of +inventing a second uninstall transport. The same identity-preservation contract applies to copied upgrade transport: shell upgrade payloads must carry `--agent-id` and `--hostname`, and PowerShell upgrade payloads must carry `PULSE_AGENT_ID` and `PULSE_HOSTNAME`, diff --git a/frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx b/frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx index 03c302b76..4d9db0c12 100644 --- a/frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx +++ b/frontend-modern/src/components/Settings/InfrastructureAgentDoctorPage.tsx @@ -18,6 +18,7 @@ import { } from '@/utils/unifiedAgentInventoryPresentation'; import { formatInfrastructureAgentDoctorReport, + getInfrastructureAgentDoctorUninstallHandoff, summarizeInfrastructureAgentDoctorTargets, type InfrastructureAgentDoctorStatus, type InfrastructureAgentDoctorTarget, @@ -373,6 +374,8 @@ export const InfrastructureAgentDoctorPage: Component action.code !== 'copy_upgrade_command', ); + const uninstallHandoff = () => + getInfrastructureAgentDoctorUninstallHandoff(target); const expanded = () => isExpanded(target); return ( @@ -526,6 +529,50 @@ export const InfrastructureAgentDoctorPage: Component + + {(handoff) => ( +
+

+ This agent was removed from Pulse, but the agent software may + still be installed on its host. Finish detaching it by running + the uninstall command on the affected host itself. Pulse does + not run commands remotely. +

+ + {(entry) => ( +
+
+ {entry.label} +
+
+ + void copyCommand( + operations.getPlatformUninstallCommand( + entry.platform, + handoff().identity, + ), + ) + } + title="Copy host-local agent uninstall command" + label={`Copy ${entry.label} uninstall command for ${target.displayName}`} + /> +
+                                            
+                                              {operations.getPlatformUninstallCommand(
+                                                entry.platform,
+                                                handoff().identity,
+                                              )}
+                                            
+                                          
+
+
+ )} +
+
+ )} +
+