diff --git a/.ai/spec/README.md b/.ai/spec/README.md new file mode 100644 index 0000000..e4b546d --- /dev/null +++ b/.ai/spec/README.md @@ -0,0 +1,23 @@ +# Lightspeed Agentic Console -- Specifications + +These specs define the requirements, behaviors, and architecture for the lightspeed-agentic-console OpenShift Console plugin. + +- **[`what/`](what/)** -- Behavioral rules: WHAT the UI must do. +- **[`how/`](how/)** -- Architecture specs: HOW the plugin is structured. + +## Scope + +These specs cover the **lightspeed-agentic-console** React/PatternFly dynamic plugin only. The operator CRD definitions are in the operator specs; this plugin renders and interacts with those CRDs. + +## Audience + +AI agents (Claude). Specs optimize for precision. + +## Quick Start + +| I want to... | Read | +|--------------|------| +| Understand proposal UI behavior | `what/proposal-ui.md` | +| Navigate the plugin codebase | `how/plugin-architecture.md` | + +Jira tracking: Feature OCPSTRAT-3095, Epic HPUX-1451. diff --git a/.ai/spec/how/README.md b/.ai/spec/how/README.md new file mode 100644 index 0000000..255dbae --- /dev/null +++ b/.ai/spec/how/README.md @@ -0,0 +1,5 @@ +# Architecture Specifications (how/) + +| Spec | Description | +|------|-------------| +| [plugin-architecture.md](plugin-architecture.md) | Dynamic plugin setup, K8s model bindings, component hierarchy, webpack config | diff --git a/.ai/spec/how/plugin-architecture.md b/.ai/spec/how/plugin-architecture.md new file mode 100644 index 0000000..4d78e9c --- /dev/null +++ b/.ai/spec/how/plugin-architecture.md @@ -0,0 +1,73 @@ +# Console plugin — implementation architecture + +Scope: OpenShift Console dynamic plugin wiring for the agentic proposals UI. Complements `what/proposal-ui.md`. CRD lifecycle semantics: `lightspeed-agentic-operator/.ai/spec/what/proposal-lifecycle.md`. + +## Module Map (`src/`) + +| Path | Purpose | +|------|---------| +| `config.ts` | `getApiUrl`: builds URLs under the plugin proxy prefix + `ols` path segment (currently unused by components). | +| `models/proposal.ts` | `K8sModel` + GVK helpers for Proposal, Agent, ProposalApproval, Analysis/Execution/Verification/Escalation Result CRDs; TypeScript types for specs/status, remediation structures, phase helpers (`derivePhaseFromConditions`, `getPhaseDisplay`, `getRiskColor`, `resultOutcome`). Comment documents desire to auto-generate from OpenAPI/CRD. | +| `utils/approval.ts` | `findStage`, `getStageStatus`, `stageNeedsApproval`, `buildApprovalPatch` (JSON Patch triples for `ProposalApproval.spec.stages`). | +| `utils/markdown.ts` | `stripUiFences`, `renderMarkdown` (marked + DOMPurify). | +| `hooks/useStageApproval.ts` | Combines derived phase + `stageNeedsApproval` with `k8sPatch` calls using `buildApprovalPatch`; loading and error state. | +| `components/proposals/ProposalListPage.tsx` | List page header, filters, watch + virtualized table. | +| `components/proposals/ProposalDetailPage.tsx` | Detail shell: watches, tab state, CMO/trigger variants, compose Overview/Proposal/Execution/Verification/Escalation tab bodies inline as subcomponents. | +| `components/proposals/EscalateModal.tsx` | Modal wrapper calling `useStageApproval(..., 'Escalation')` approve; no text fields. | +| `components/proposals/SandboxLogViewer.tsx` | Pod watch + `consoleFetch` streaming log client + UI (auto-scroll switch, status badge). | +| `components/proposals/PhaseIcon.tsx` | PatternFly icons per phase + failure overlays for completed + failed results. | +| `components/proposals/MarkdownText.tsx` | Memoized `renderMarkdown` → `dangerouslySetInnerHTML`. | +| `components/proposals/DynamicComponent.tsx` | Re-export from `dynamic/`. | +| `components/proposals/dynamic/index.tsx` | `DynamicComponent` switch: maps `type` strings to visualization, diff, picker, evidence, timeline, CMO components; unknown → warning + JSON. | +| `components/proposals/dynamic/types.ts` | Prop interfaces for dynamic components. | +| `components/proposals/dynamic/Visualization.tsx` | `QueryBrowser` path vs static Victory chart path; uses `DataTable` for optional summary. | +| `components/proposals/dynamic/DataTable.tsx` | Plain HTML table with optional monospace first column. | +| `components/proposals/dynamic/EvidenceTable.tsx` | Card + `DataTable`. | +| `components/proposals/dynamic/ResourceDiff.tsx` | Side-by-side `CodeBlock` JSON. | +| `components/proposals/dynamic/ActionPicker.tsx` | Selectable cards + confirm button calling optional `onAction`. | +| `components/proposals/dynamic/StatusTimeline.tsx` | Vertical list with `statusIcon`. | +| `components/proposals/dynamic/CmoComponents.tsx` | CMO diagnosis, metric evidence, remediation step, trigger proposal (builds `PrometheusRule` via `k8sCreate`). | +| `components/proposals/dynamic/utils.ts` | Timespan presets, chart palette, formatters, timeline icons, severity color, name sanitizer for PrometheusRule metadata. | +| `components/proposals/proposal-detail.css` | Tab + layout classes (`ols-plugin__*` prefix). | +| `components/proposals/sandbox-log-viewer.css` | Log viewer layout. | +| `test-setup.ts`, `test-helpers.ts` | Vitest harness. | +| `models/proposal.test.ts`, `utils/approval.test.ts`, `hooks/useStageApproval.test.ts` | Unit tests. | +| `__mocks__/dynamic-plugin-sdk.ts` | SDK stub for tests. | + +Repository root: `console-extensions.json` (routes + nav), `webpack.config.ts` (ConsoleRemotePlugin, ts-loader, locales copy), `package.json` (`consolePlugin.exposedModules`). + +## Data Flow + +1. **List** — `useK8sWatchResource` list on `LightspeedProposalGVK` → filter by derived phase → row links to `/lightspeed/proposals/:ns/:name`. +2. **Detail** — Parallel watches: Proposal, ProposalApproval, Agent list, four result CR lists with label selector → derive phase → compute `latest*` results via last step result ref → pass into tab bodies. +3. **Approval** — `useStageApproval` computes need from conditions + existing stages + terminal phases → `k8sPatch` JSON Patch on ProposalApproval (append stage path chosen by whether `spec` / `spec.stages` exists). +4. **Refine** — Direct `k8sPatch` on Proposal `spec.revisionFeedback` from Proposal tab. +5. **Logs** — Sandbox pod name/namespace from `status.steps.*.sandbox` → core Pod watch → GET pod log stream with query params (`container`, `follow`, `tailLines` or `sinceTime`). +6. **Dynamic UI** — Analysis result options carry `components` arrays → `AdapterComponents` filters to known `type` set in ProposalDetailPage → `DynamicComponent` dispatches. + +## Key Abstractions + +- **GVK constants** — `LightspeedProposalGVK`, `LightspeedProposalApprovalGVK`, `AnalysisResultGVK`, `ExecutionResultGVK`, `VerificationResultGVK`, `EscalationResultGVK` align group/kind/version with models for watches. +- **Models** — `K8sModel` entries: `apiGroup` `agentic.openshift.io`, `apiVersion` `v1alpha1`, plural forms `proposals`, `agents`, `proposalapprovals`, `analysisresults`, `executionresults`, `verificationresults`, `escalationresults`. +- **Phase parity** — `derivePhaseFromConditions` annotated to match Go `DerivePhase`. +- **Approval patch shape** — RFC 6902-style operations: add `/spec/stages/-` or initialize `/spec/stages` or `/spec` with first stage; stage payload includes nested spec per stage type. +- **Result outcome** — `resultOutcome` reads `Completed` condition reason `Succeeded` / `Failed`. + +## Integration Points + +- **Console dynamic plugin SDK** — `useK8sWatchResource`, `k8sPatch`, `k8sCreate`, `ResourceLink`, list/table primitives, `QueryBrowser`, `consoleFetch`. +- **PatternFly** — Layout, cards, labels, charts (Victory wrappers), modal. +- **Routing** — `react-router-dom` v5 + `useParams` with pathname fallback parsing. +- **i18n** — `react-i18next` namespace `plugin__lightspeed-agentic-console-plugin`. +- **Plugin registration** — `console-extensions.json`: routes `/lightspeed/proposals`, `/lightspeed/proposals/:ns/:name`; nav href under Administration perspective labeled via i18n key. +- **Exposed modules** — `package.json` `consolePlugin.exposedModules` maps `ProposalListPage` and `ProposalDetailPage` only. +- **HTTP proxy** — `API_BASE_URL` pattern `/api/proxy/plugin/lightspeed-agentic-console-plugin/ols` for future backend calls; Kubernetes traffic uses `/api/kubernetes/...` in log viewer. +- **PrometheusRule creation** — `monitoring.coreos.com/v1` `PrometheusRule` from CMO trigger proposal data (inline model struct in `CmoComponents.tsx`). + +## Implementation Notes + +- Webpack uses `ConsoleRemotePlugin` with empty entry; context `src/`; federated remote consumed by console host; `CopyWebpackPlugin` copies `locales/`. Dev server port and CORS headers configured for bridge/container workflows. +- TypeScript types for CRDs are hand-maintained; `proposal.ts` TODO references OpenAPI codegen from cluster or CRD YAML. +- Tests mock `@openshift-console/dynamic-plugin-sdk` for isolation. +- `KNOWN_COMPONENT_TYPES` set in ProposalDetailPage must stay aligned with `DynamicComponent` switch aliases (including `lightspeed_*` prefixed names). +- `DynamicComponent` accepts legacy short type names (`visualization`, `prometheus_query`, etc.) and `lightspeed_*` equivalents for the same renderers. diff --git a/.ai/spec/what/README.md b/.ai/spec/what/README.md new file mode 100644 index 0000000..8a51618 --- /dev/null +++ b/.ai/spec/what/README.md @@ -0,0 +1,5 @@ +# Behavioral Specifications (what/) + +| Spec | Description | +|------|-------------| +| [proposal-ui.md](proposal-ui.md) | Proposal list/detail views, approval UX, phase derivation, dynamic components, sandbox logs | diff --git a/.ai/spec/what/proposal-ui.md b/.ai/spec/what/proposal-ui.md new file mode 100644 index 0000000..e598a0a --- /dev/null +++ b/.ai/spec/what/proposal-ui.md @@ -0,0 +1,74 @@ +# Proposal UI — behavioral specification + +Scope: proposal list, detail, multi-stage approval UX, sandbox log streaming, structured “dynamic” renderers, markdown safety. Type shapes and CRD fields are defined in the operator and mirrored in TypeScript for the console only as needed for UI bindings — see `lightspeed-agentic-operator` CRD/OpenAPI docs; phase derivation must stay in sync with `lightspeed-agentic-operator/.ai/spec/what/proposal-lifecycle.md` (operator `DerivePhase`). + +## Behavioral Rules + +1. The list page watches all namespaced `Proposal` resources and presents a virtualized table row per object with sortable columns: name (links to detail), phase (derived and displayed per rules 8–17), request text (truncated after a fixed preview length with ellipsis), namespace, and age from `metadata.creationTimestamp`. +2. List phase filtering uses the same derived phase as the phase column; filter options exclude at least one operator-derived phase value that the list can still display when derived (filter set and derived phase set can disagree). +3. The detail page watches a single `Proposal` by route params (`namespace`, `name`), the matching namespaced `ProposalApproval`, cluster-scoped `Agent` list (for names), and namespaced `AnalysisResult`, `ExecutionResult`, `VerificationResult`, and `EscalationResult` lists filtered by label selector `agentic.openshift.io/proposal` = proposal name. +4. The detail header shows proposal name, a phase badge (color and label per rule 16), and `PhaseIcon` (per rule 17); it does not duplicate namespace in the header row (namespace appears in the Overview tab). +5. Tab set is chevron-style: `Overview`, `Proposal`, `Execution`, `Verification`, and conditionally `Escalation`. `Escalation` is omitted unless an `Escalated`-typed condition exists on the proposal. For CMO-sourced alert proposals (label `ols.openshift.io/source` = cluster-monitoring-operator, and not trigger-bootstrap), `Overview` is hidden and the default tab selection treats `overview` as `proposal`. +6. A “trigger-bootstrap” proposal (label `ols.openshift.io/proposal-type` = trigger-bootstrap) renders a dedicated simplified layout: trigger display name from `ols.openshift.io/trigger-name` label or metadata name, phase badge, optional analysis sandbox logs, and structured options rendered only via adapter `components` (no standard tab chrome). +7. The Overview tab shows phase (icon + badge), markdown-rendered `spec.request`, optional external source link from annotations `ols.openshift.io/source-url` / `ols.openshift.io/source-name`, `spec.targetNamespaces` when non-empty, formatted creation time, and sandbox claim display for analysis, execution, and verification steps when `status.steps.*.sandbox.claimName` is set. +8. **Phase derivation** (must match operator `DerivePhase` and `proposal-lifecycle.md`): Evaluate `status.conditions` in a fixed precedence chain; mirror the operator implementation. +9. If there are no conditions, derived phase is `Pending`. +10. If an `Escalated` condition exists with status `True`, derived phase is `Escalated`. +11. If a `Denied` condition exists with status `True`, derived phase is `Denied`. +12. If an `Escalated` condition exists but its status is not `True`: when status is `Unknown`, derived phase is `Escalating`; otherwise derived phase is `Failed`. +13. If a `Verified` condition exists: when status is `True`, derived phase is `Completed`; when `Unknown`, `Verifying`; when status is neither, if reason is `RetryingExecution` then `Executing`, else `Failed`. (When this condition row exists, earlier branches did not return.) +14. If no `Verified` condition exists and an `Executed` condition exists: when status is `True`, `Verifying`; when `Unknown`, `Executing`; otherwise `Failed`. +15. If no `Executed` condition exists and an `Analyzed` condition exists: when status is `True`, `Proposed`; when `Unknown`, `Analyzing`; otherwise `Failed`. If no condition in the chain matched, derived phase is `Pending`. +16. **Phase display mapping** for badges (`getPhaseDisplay`): `Pending` → grey / “Pending”; `Analyzing` → blue / “Analyzing”; `Proposed` → teal / “Proposed”; `Executing` → purple / “Executing”; `Verifying` → orange / “Verifying”; `Escalating` → orange / “Escalating”; `Completed` → green / “Completed”; `Failed` → red / “Failed”; `Denied` → red / “Denied”; `Escalated` → orangered / “Escalated”; unknown string → grey / raw string or “Unknown”. +17. `PhaseIcon` maps a subset of phases to distinct icons; phases without an explicit case reuse the default idle icon. `Completed` shows a warning icon instead of success when execution or verification result outcome is failed (parallel display nuance). +18. **Analysis approval**: When `ProposalApproval` exists, the proposal is not in a terminal derived phase, no `Analysis` stage is recorded in `ProposalApproval.spec.stages`, and (`Analyzed` condition is missing OR its status is not `True`), the UI offers approve/deny for analysis (optional agent dropdown from cluster agents, defaulting to `spec.analysis.agent`). Approving patches `ProposalApproval` with a new `Analysis` stage without `decision: Denied`. Denying adds `decision: Denied`. +19. **Execution approval**: When `Analyzed` is `True`, no `Executed` condition row exists yet, a terminal phase is not active, and no `Execution` stage is recorded, the UI requires choosing a remediation option index when multiple options exist, supports approve-with-retries vs approve-without (maps to `maxAttempts` on the patch only when positive), optional execution agent selection defaulting to `spec.execution.agent`, and deny. Advisory proposals (`spec.execution` absent) still show execution approval affordances but display informational copy that cluster execution is skipped. Approving sends `Execution` stage with selected `option` index and optional `maxAttempts` / `agent`. +20. **Verification approval gate** (`stageNeedsApproval`): When `Executed` is `True`, the `Verified` condition row is absent, the derived phase is not terminal, and no `Verification` stage is recorded, the hook reports that verification needs approval. +21. **Verification tab vs approval UI**: The verification **ApprovalCard** is shown only in the branch where there is no verification result CR yet **and** no verification sandbox pod; once a sandbox pod exists, the tab shows streaming/result layout even if approval were still pending in another operator state. Escalation tab follows the same pattern for its approval card. +22. **Escalation approval**: When `Escalated` condition status is `Unknown`, no `Escalation` stage exists yet, and derived phase is not terminal, the UI offers escalation approve/deny (optional agent; default in card copy references analysis agent). A separate modal launched from the verification tab confirms escalation approval with the same hook; it does not collect freeform escalation text. +23. If any stage records `decision: Denied`, that stage’s card state reads “denied”; otherwise an existing stage reads “approved”. A user **Deny** patch records `decision: Denied` on that stage; terminal derived phase `Denied` occurs when the operator sets condition `Denied: True` (see operator lifecycle spec — console does not set conditions). +24. Tabs show a “Needs approval” badge when that tab aggregates a stage awaiting approval (`Proposal` tab if analysis or execution needs approval; `Verification` / `Escalation` similarly). A passive “active phase” dot appears on the tab matching the current derived phase when that tab is not selected and approval is not pending there. +25. **Proposal tab content**: If `spec.revisionFeedback` is set and proposal `metadata.generation` exceeds `Analyzed`’s `observedGeneration`, show re-analysis waiting state with feedback alert and analysis sandbox logs when available. Otherwise if analysis results have no options: show analysis approval card (if sandbox not present), or analyzing logs card, or terminal/non-terminal empty messages. When options exist: optional collapsible analysis sandbox logs (auto-collapses when analysis data first appears unless revision pending), then remediation options with diagnosis/proposal/RBAC/verification/adapter components. Execution approval controls appear under the selected option. **Refine** opens inline textarea patching `spec.revisionFeedback` on the `Proposal` (not the escalation modal). +26. **Execution tab**: Streams execution sandbox logs (or placeholder messages) until an `ExecutionResult` exists; then shows structured execution outcome, actions taken, and post-execution verification summary as markdown when present. +27. **Verification tab**: Follows rule 21 for approval vs logs/result; shows verification outcome label, markdown summary, expandable checks with CLI-like source/value display when a result exists. If verification outcome failed, show **Escalate** opening the escalation modal. +28. **Escalation tab**: Parallel pattern: approval card (per rule 21), logs, result with summary, expandable full content markdown, failure reason alert. +29. “Latest” result CR for each step is resolved by taking the last `status.steps.*.results` ref name and finding the CR with that `metadata.name` in the watched list. +30. **Sandbox log viewer**: Watches core `Pod`; when phase is `Running`, opens a follow log stream for container name `agent` via console-authenticated Kubernetes log API; buffers chunks, trims retained text when exceeding an internal maximum, supports reconnect with exponential backoff capped at an internal maximum, toggles auto-scroll, and shows connection state badges (searching, waiting, streaming, ended, reconnecting, error). If pod missing, transitions to ended/searching appropriately. Default namespace for sandbox when `status.steps.*.sandbox.namespace` is empty is `openshift-lightspeed`. +31. **Markdown** (`MarkdownText`): UI-oriented fenced blocks matching a `ui:*` code-fence pattern are stripped (including a trailing partial fence) before parsing; remainder is parsed as markdown and sanitized to HTML before injection. +32. **Remediation risk label colors**: `low` → green; `medium` → orange; `high` or `critical` → red; other → grey. Diagnosis **confidence** labels use a separate mapping (high → green, medium → orange, low → red). +33. **Dynamic / adapter components**: Objects under `RemediationOption.components` (or trigger-bootstrap options) render each element. If `type` is in the plugin’s known set, a typed renderer is used; otherwise compact JSON is shown. Known types include lightspeed Prometheus/metrics, resource diff, action picker, evidence table, status timeline, and CMO alert/metric/remediation/trigger types (see how-spec for dispatch table). `ActionPicker` supports `onAction` when wired; remediation embeddings omit a handler so confirming a selection does not send feedback from the console. +34. `ResourceDiff` displays pretty-printed JSON snapshots of `before` / `after` maps side by side (not a line-oriented unified diff). +35. `Visualization` either embeds console `QueryBrowser` when `queries` is non-empty (optional summary `DataTable` above) or renders a static chart from `series` data. +36. `EvidenceTable` wraps `DataTable` with a titled card and monospace styling on the first column by default. +37. `StatusTimeline` lists timestamp, label, and icon per event severity bucket (`success`, `danger`, `warning`, `info`). + +## Configuration Surface + +- `Proposal.metadata.annotations` (console model: `LightspeedProposalModel`): `ols.openshift.io/source-url`, `ols.openshift.io/source-name` (external link in Overview). +- `Proposal.metadata.labels`: `ols.openshift.io/source`, `ols.openshift.io/proposal-type`, `ols.openshift.io/trigger-name` (layout and naming). +- `Proposal.spec.request`, `targetNamespaces`, `revisionFeedback`, `analysis.agent`, `execution` presence (advisory), `execution.agent`, `verification.agent`. +- `Proposal.status.conditions`, `status.steps.*` (sandbox claims, result refs). +- `ProposalApproval.spec.stages[]`: `type`, `decision`, `analysis`, `execution` (`option`, `maxAttempts`, `agent`), `verification`, `escalation`. +- `AnalysisResult.status.options[]`: remediation payloads including `components`. +- Execution / verification / escalation result CRs: standard status fields consumed by tabs. +- Log streaming: Kubernetes path namespace and pod name from sandbox status; container name fixed to `agent`. + +## Constraints + +- UI must not diverge from operator phase semantics; when in doubt, treat `proposal-lifecycle.md` as authoritative. +- Do not duplicate full CRD field inventories here; mirror types in `src/models/proposal.ts` only as needed. +- English copy is i18n-keyed under `plugin__lightspeed-agentic-console-plugin`. +- API URL helper `getApiUrl` exists for a console-proxy prefix; no component references it yet (extension HTTP surface is unused for these flows). + +## Planned Changes + +- [PLANNED: OLS-3022] Remove or replace diagnosis **confidence** presentation per transparency initiative. +- [PLANNED: OLS-3014] Tech Preview badge in console shell context for agentic surfaces. +- [PLANNED: OLS-3015] AI content tagging conventions in UI. +- [PLANNED: OLS-3016] Disclaimer placement for AI-generated content. +- [PLANNED: OLS-2963–OLS-2973] AG-UI Chat integration for agentic OLS (not present in current proposal routes). +- [PLANNED: HPUX-1451] Broader Agentic AI UX alignment with console platform patterns. +- [PLANNED: OCPSTRAT-3095] Feature-level UX requirements as epics land. +- Align list phase filter IDs with full derived phase enum (e.g. include `Proposed`) for consistent filtering. +- [PLANNED: UX] Escalation modal freeform feedback if product requires user context in addition to operator-driven escalation flow. +- Wire `ActionPicker` to a chat/feedback channel when AG-UI or equivalent exists.