Files
kandev/docs/plans/kubernetes-executor-status-settings-repair/plan.md
T
2026-09-02 13:50:03 +00:00

13 KiB

spec, created, status
spec created status
docs/specs/kubernetes-executor/spec.md 2026-08-31 complete

Implementation Plan: Kubernetes Executor Status and Settings Repair

Overview

The isolated seed instance reproduces two different answers for the same live Pod: task.session.status returns remote_status_error: "... Unauthorized", while GET /api/v1/kubernetes/executors/:id/sessions returns that exact Pod as Running. The generic path reaches KubernetesExecutor.RefreshRemoteInstance, whose inspectKubernetesRefresh reads through the launch-time kubernetesSession.runtime; the exact Kubernetes endpoint creates a client from the current executor configuration. RemoteCloudTooltip then caches the first error for the lifetime of the session row. Separately, the first UX repair deliberately kept KubernetesConnectionCard on a dedicated executor route, made the profile page link to it, and rendered task actions as a prominent footer. Those choices directly conflict with the updated product contract.

The repair first makes Kubernetes lifecycle inspection credential-fresh. It then moves list/card status to the same exact task/session Pod API used by the task page, compacts the task disclosure controls, and composes shared connection plus profile configuration on one profile-root settings page. The persisted executors and executor_profiles resources remain separate.


Backend

Refresh active Kubernetes clients before status and reconnect inspection

  • Update apps/backend/internal/agent/runtime/lifecycle/executor_kubernetes_refresh.go so KubernetesExecutor.RefreshRemoteInstance does not treat the runtime client captured at launch as permanent credential authority. Build a fresh kubernetesRuntimeClient from the recorded connection metadata before the exact Pod/PVC inspection, validate the same recorded namespace/name/UID and ownership labels, and publish the refreshed runtime only after that inspection succeeds.
  • Preserve the existing session, loopback forward, agentctl client, nonce, and token when the fresh inspection proves no process or transport refresh is needed. If a restart or dead-forward refresh is needed, stage the replacement using the fresh runtime so future exec and port-forward operations use the repaired credentials.
  • Keep authorization failures causal and sanitized. A fresh client that is still forbidden or unauthorized must remain an error; the repair must not convert a real RBAC failure into a healthy status or fall back to unrelated cluster resources.
  • Reuse kubernetesExecutorConfigFromMetadata, clientFactory, kubernetesCleanupInventory, verifyRecordedPod, and verifyKubernetesRecordedPVC. Do not add a second Kubernetes client builder or weaken exact-inventory checks.

Remote status orchestration

  • Keep Manager.pollOneRemoteStatus as the owner of opportunistic refresh then GetRemoteStatus. Add a manager-level regression only if needed to prove that a successful fresh inspection replaces a previously cached unauthorized result for GetRemoteStatusBySessionID.
  • Do not change the task.session.status wire shape or expose Kubernetes API errors beyond the existing sanitized remote_status_error field.

Frontend

Sidebar and Kanban Kubernetes status

  • Extend RemoteCloudTooltip in apps/web/components/task/remote-cloud-tooltip.tsx with the exact executor ID. For executorType === "k8s", call getKubernetesTaskSession(executorId, taskId, sessionId) and project its Pod name, phase/container state, creation time, and sanitized failure reason into the existing tooltip view model. Other remote executors continue to use task.session.status.
  • Refetch when a hover reopens instead of using fetchedSessionId as a permanent cache key. Deduplicate only the currently in-flight request and discard responses for an obsolete task/session scope.
  • Thread primaryExecutorId through buildSidebarItem, TaskSwitcherItem, TaskSwitcherRow, and TaskItem; Kanban cards already carry the field. A genuine exact-Pod failure remains destructive, while a repaired credential read clears the old error and restores the healthy Pod glyph/tone.

Compact task disclosure controls

  • Refactor apps/web/components/task/executor-environment-disclosure.tsx and apps/web/components/task/executor-environment-info.tsx so Kubernetes Refresh and Profile settings are quiet icon-only controls in the Pod summary header. Remove the sticky two-button footer and reduce the desktop popover width in executor-settings-button.tsx while retaining the existing summary content.
  • Keep the existing single-flight useTaskEnvironment.refresh contract. The refresh icon must expose aria-busy, spin while awaiting the joined request, and remain clickable after the request settles.
  • Resolve the settings destination with executorProfileSettingsPath({ id: env.executor_id, type: env.executor_type }, env.executor_profile_id). Keep a safe executor-level fallback only for malformed legacy rows with no profile identity.
  • Preserve a 40 px desktop hit target and at least 44 px on coarse-pointer Drawer layouts, plus translated accessible names and tooltips.

One Kubernetes profile settings hierarchy

  • Replace the summary-and-link composition in apps/web/components/settings/kubernetes-profile-cluster-section.tsx with the inline KubernetesConnectionCard, followed by diagnostics and executor-wide sessions. Diagnostics serialize both the unsaved connection form and unsaved Kubernetes profile form.
  • In apps/web/app/settings/executors/[profileId]/page.tsx, own connection form state alongside the existing profile form and register one Kubernetes page save contributor. It must:
    • validate both forms;
    • load active-session impact once and show one confirmation for the dirty connection/workload scopes;
    • save the executor connection before the profile when both are dirty;
    • update each baseline after its own successful call so a later partial failure leaves only the unsaved resource dirty; and
    • reset both forms and diagnostics from the shared floating save control.
  • Reuse useKubernetesExecutorResource, KubernetesConnectionCard, useKubernetesSessionImpact, serializeKubernetesExecutorConfig, and the existing profile serializer. Extend kubernetes-save-confirmation.ts with combined copy rather than displaying two browser confirmations during one Save action.
  • Keep members on the same page with disabled connection/profile controls, disabled diagnostics, and readable active sessions.

Navigation and legacy recovery

  • Make configured Kubernetes executor nodes in settings-menu-branches.ts expand to profiles instead of navigating to a second connection page. The exact profile rows remain the canonical settings destinations.
  • Change the task popover gear to the exact profile path. Make /settings/executors/k8s/:executorId redirect to a deterministic profile when one exists. Retain the existing standalone page only for an orphan executor with no profiles so administrators can repair or delete it.
  • Update LegacyExecutorSettingsRoute and route helpers/tests without changing non-Kubernetes executor routes.

Tests

  • What: a launch-time runtime client returns Unauthorized, a fresh client built from the same recorded connection metadata can inspect the exact Pod, and refresh/status becomes healthy without replacing the running agentctl transport. File: apps/backend/internal/agent/runtime/lifecycle/executor_kubernetes_restart_test.go. How: fake runtime factory returning stale then current resource clients; assert exact inventory verification, runtime publication, no new Pod, and no unnecessary forward/nonce rotation.
  • What: a fresh client that remains unauthorized or returns a foreign Pod stays an error and never publishes the new runtime. File: the same lifecycle test file. How: table-driven negative cases.
  • What: Kubernetes sidebar/Kanban status uses the exact session API and refetches after reopening, clearing a prior unauthorized result; other remote executors retain the WS request. File: apps/web/components/task/remote-cloud-tooltip.test.tsx plus mapping tests for sidebar/Kanban props. How: deferred API/WS mocks with close/reopen and stale-response cases.
  • What: the task disclosure has compact icon controls, visible joined refresh progress, no action footer, and an exact profile-root link on desktop/mobile. File: executor-settings-button.test.tsx and use-task-environment.test.tsx. How: Testing Library interaction tests.
  • What: the Kubernetes profile root renders editable/read-only shared connection fields, diagnostics, and sessions before profile sections; one save confirmation coordinates connection plus workload and represents a partial failure correctly. File: kubernetes-profile-cluster-section.test.tsx, a focused new combined save-contributor test, and apps/web/src/settings-routes.test.tsx. How: hook/component tests with ordered deferred mutations.
  • What: Kubernetes tree/legacy routes resolve to profile roots while non-Kubernetes routing and orphan-executor recovery remain unchanged. File: settings-menu-branches.test.ts, legacy-executor-settings-route.test.tsx, and executor-settings-routes tests.

E2E Tests

  • Scenario: GIVEN a Kubernetes task in sidebar and Kanban chrome, WHEN the first hover response is unauthorized and a later exact Pod read succeeds, THEN reopening shows healthy Pod state without the red cached error. File: apps/web/e2e/tests/settings/kubernetes-task-environment.spec.ts using a narrowly scoped WebSocket/API response rewrite helper.
  • Scenario: GIVEN a running Kubernetes task, WHEN the desktop hover or mobile Drawer opens and Refresh/settings are selected, THEN the compact controls are accessible, Refresh visibly awaits the causal read, and settings opens /settings/executors/:profileId. Files: desktop and mobile Kubernetes task-environment specs.
  • Scenario: GIVEN an administrator or member opens a Kubernetes profile, WHEN the page renders, THEN connection configuration, diagnostics, and active sessions are first-class sections on that one page; admin save/reset and member read-only behavior work without navigating to a connection subpage. Files: kubernetes-executor.spec.ts and mobile-kubernetes-executor.spec.ts.
  • Scenario: GIVEN a legacy Kubernetes connection URL for an executor with a profile, WHEN it loads, THEN it redirects to that profile root. An executor without profiles still mounts the recovery editor. File: the desktop settings E2E spec.

Public Documentation

  • Update docs/public/k8s.md and docs/public/executors.md to describe the inline shared connection editor, one profile settings hierarchy, compact task controls, and profile-root navigation.
  • Keep the warning that executor connection values are shared across profiles and that saved connection changes can affect active-session reconnects.

Verification Results

Implementation is complete. Task-level records contain the RED/GREEN evidence and focused command results. The final integrated gates passed with 14 focused frontend files and 97 tests, 4/4 desktop Playwright scenarios, 4/4 mobile Chrome Playwright scenarios, TypeScript typecheck, the E2E sleep ratchet, 61 public-doc validator tests, 41 validated published pages, and a clean git diff --check. Managed E2E processes and current-run temporary directories were torn down; the main Kandev instance and the separately seeded test instance remained intact.


Implementation Waves And Parallel Candidates

The default is sequential execution in the primary conversation. These tasks share lifecycle and settings contracts, so none is marked parallel-safe.

Wave 1:

Wave 2:

Wave 3:

Wave 4:


Risks And Constraints

  • A fresh credential client may point at a different cluster if an administrator replaces the kubeconfig target in place. Exact namespace, Pod name, UID, and all ownership labels remain mandatory before publication; mismatch fails closed.
  • Executor connection and profile persistence are not transactional. The UI must accurately retain partial-save state rather than claiming all changes were rolled back.
  • One executor can own several profiles. Inline copy and confirmation must make the shared blast radius explicit without duplicating a connection subpage.
  • The orphan-executor recovery route remains intentionally exceptional so removing the normal subpage does not strand an executor with zero profiles.

Out Of Scope

  • Database migrations or merging executors with executor_profiles.
  • New Kubernetes lifecycle controls, Pod deletion/restart actions, or changes to exact resource-ownership policy.
  • Exposing kubeconfig contents, tokens, or unsanitized Kubernetes API errors.