Commit graph

2591 commits

Author SHA1 Message Date
rcourtman
f83d25d155 Add Discovery provenance markers 2026-05-20 13:32:34 +01:00
rcourtman
9c55c341e2 Implement Discovery observed context UX 2026-05-20 13:21:55 +01:00
rcourtman
435cf816fb Harden Discovery command-scan gating 2026-05-20 12:40:04 +01:00
rcourtman
1ce47e21a0 Add sortable storage table headers 2026-05-20 00:29:33 +01:00
rcourtman
a1b862ba9f Replace add-filter menu with direct selector 2026-05-20 00:26:55 +01:00
rcourtman
fb97f83574 Tighten platform table resource drawers 2026-05-19 22:42:20 +01:00
rcourtman
86ac84f68e Keep inline drawer expansion local 2026-05-19 19:36:22 +01:00
rcourtman
34d53b23ea Scope platform filters to page context 2026-05-19 18:51:59 +01:00
rcourtman
2d62ae5f19 Make platform filters page-specific 2026-05-19 18:00:43 +01:00
rcourtman
7652e2833e Promote vSphere from admitted to supported
Flip vmware-vsphere from governance_state=admitted, readiness_stage=
first-lab-ready to supported on both axes. The phase-1 floor (vCenter
client, canonical agent/vm/storage projection, alerts integration,
Assistant read paths, mock fixtures, page surface, automated proof for
the read-only boundary) has been in place; the only remaining gate was
a live vCenter run, and we are taking the platform claim on the strength
of the implementation rather than blocking on that proof.

Add vmware-vsphere to default_infrastructure_source_order, to the
First-class platforms list and Current Support Matrix in
PLATFORM_SUPPORT_MODEL, and to the Pulse primary navigation
(automatically via SUPPORTED_PLATFORM_IDS in the regenerated frontend
manifest). Rename and invert the Go contract test that asserted vSphere
stays admitted, and let the admitted-platform helpers tolerate an empty
set now that nothing currently lives there. Drop the "in first-lab-
ready readiness" line from the vSphere empty state.

PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT used: this commit only flips two
JSON values for one platform; no subsystem contract schema or surface
changed.
2026-05-19 17:27:06 +01:00
rcourtman
aa1f07bd94 Left-align workload image column 2026-05-19 16:16:59 +01:00
rcourtman
4d11449cd8 Compact container image names 2026-05-19 15:41:34 +01:00
rcourtman
00b98cf08e Canonicalize runtime badge tones 2026-05-19 15:33:08 +01:00
rcourtman
4156c66059 Clarify Docker workload host identity 2026-05-19 15:18:39 +01:00
rcourtman
90482607da Bind Proxmox install command tokens on first use 2026-05-19 09:47:05 +01:00
rcourtman
391cea1b13 Fix managed hot-dev LAN browser binding 2026-05-19 09:08:32 +01:00
rcourtman
0c1cdf7488 Add opt-in Proxmox LXC Docker inventory 2026-05-18 23:44:24 +01:00
rcourtman
91d7102a27 Make managed hot-dev verification deterministic 2026-05-18 17:29:50 +01:00
rcourtman
6feb85245f Stabilize first-session settings handoffs 2026-05-18 17:27:03 +01:00
rcourtman
84f627e22a Move Proxmox host drawers to the host table 2026-05-18 17:19:32 +01:00
rcourtman
5a405f7f07 Protect inside-guest Docker visibility boundaries 2026-05-18 14:16:15 +01:00
rcourtman
8302229a26 Refine workload sparkline I/O density 2026-05-18 11:29:15 +01:00
rcourtman
1ebcf37525 Make Docker I/O columns telemetry-aware 2026-05-18 11:08:00 +01:00
rcourtman
5fb583d26f Use Docker-native container workload columns 2026-05-18 10:50:34 +01:00
rcourtman
68431aaaf1 Hide Docker container disk column by default 2026-05-18 10:38:19 +01:00
rcourtman
fe7b618e8f Surface identified service in GuestDrawer overview
Discovery records carry rich identification (service name, category,
ports, cli access, paths) but until now they only rendered inside the
Discovery sub-tab. Operators opening a workload drawer to look at memory
or backups had no signal that Pulse already knows the LXC is "Homepage
Dashboard" or the Docker container is "Proxmox Backup Server" — they
had to switch tabs to see it.

Add an "Identified Service" card to GuestDrawerOverview that shows the
service name, category, port count, CLI access hint, and confidence
percent. The card mirrors the existing System/Guest Info/Backup card
shapes so it slots into the same flex grid.

useGuestDrawerState passively loads the discovery record via getDiscovery
(no scan triggered — manual scans, progress UI, and approval prompts
stay owned by DiscoveryTab and useDiscoveryTabState). A new presentation
helper, getDiscoveryIdentifiedSummary, packages the record into the
display shape and returns null when the record has no meaningful
identification — same gate as the Discovery tab's hasValidDiscovery, so
both surfaces render or hide together.

Contracts: amend frontend-primitives Completion Obligations to fence the
new identified-service reducer to discoveryPresentation.ts, and amend
performance-and-scalability Current State to require that out-of-tab
discovery surfacing stay passive (no scans). Verification via
discoveryPresentation.test.ts and GuestDrawer.test.tsx covers the
populated/empty/null branches and the drawer card render.

Workloads table row surfacing is the natural next step but blocked
behind the parallel agent's in-progress WorkloadPanel/WorkloadsSurface/
useWorkloadsState changes; revisit after those land.
2026-05-17 23:21:11 +01:00
rcourtman
06fd4fc89e Bypass approval gate for trusted internal Discovery commands
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Discovery wraps every probe in `docker exec <container> sh -c '...'`.
The agentexec command policy lists `^docker\s+exec\s` as RequireApproval
(a sound default for user-driven docker exec) and Discovery has no path
to mint or supply an ApprovalID. Result: every probe was rejected, the
scanner returned empty CommandOutputs, and the AI fell back to
"Unknown Infrastructure Resource" at confidence 0. The Discovery sub-tab
rendered empty after a "successful" run.

Add a Trusted bool to ExecuteCommandPayload on both the server-facing
agentexec type and the agent's wire struct. When set, the approval gate
is skipped on both ends and the server does not attempt to auto-mint an
approval grant (which would fail with "approval id is required").
PolicyBlock still applies; this is not a way to run arbitrary commands.

Only the discoveryCommandAdapter sets Trusted=true. The flag is never
populated from a deserialised HTTP body or any user-driven path. Patrol
fixes, Assistant remediation, and AI tool calls continue to flow through
the governed approval-record path with a real ApprovalID.

Contracts: amend agent-lifecycle Completion Obligations and Current
State to document the lone exception to the on-agent approval rail, and
amend ai-runtime to fence the Trusted flag to the discovery adapter
only.
2026-05-17 21:59:39 +01:00
rcourtman
d20671a736 Expose Discovery model override in AI settings
The backend has had DiscoveryModel + GetDiscoveryModel() with the
shared-default fallback alongside ChatModel and PatrolModel, but the
settings UI only surfaced Assistant and Patrol overrides. Discovery
silently used whatever the shared default was set to, with no way to
dial it independently — yet Discovery is the surface where a cheaper
model (e.g. Haiku) is most often the right tradeoff.

Thread discoveryModel through the form state, load, save payload, and
failure analyzer, and render a third picker in the Model Overrides
section mirroring the Assistant/Patrol pattern. Generalize the section
header and shared-default copy so they no longer enumerate surfaces.

Extend the frontend-primitives contract to recognize Discovery alongside
Assistant and Patrol as a per-surface override, and lock the picker
presence into settingsArchitecture.test.ts.
2026-05-17 20:39:46 +01:00
rcourtman
0c9ea7504e Normalize inactive Docker Swarm evidence 2026-05-17 18:45:24 +01:00
rcourtman
29b8d5161d Refine Docker platform runtime presentation 2026-05-17 18:41:22 +01:00
rcourtman
dc0635606d Keep Pulse runtime boundaries model-owned 2026-05-17 18:24:13 +01:00
rcourtman
251e8844dc Gate platform navigation by resource evidence 2026-05-17 18:16:15 +01:00
rcourtman
2cb94d66f1 Capture platform page overlap decision 2026-05-17 18:14:26 +01:00
rcourtman
5e789f9a1d Align AI docs with model-owned reasoning 2026-05-17 17:59:22 +01:00
rcourtman
a31df2c2c2 Restore compact v5-style filter rails 2026-05-17 17:51:37 +01:00
rcourtman
6bdc10d74f Retire dead infrastructure connections table 2026-05-17 15:54:49 +01:00
rcourtman
0fbb8b7124 Render compact per-disk usage lanes 2026-05-17 14:59:03 +01:00
rcourtman
41349be7d8 Fix multi-disk pressure bars 2026-05-17 14:39:57 +01:00
rcourtman
484d9aab87 Fix dev backend binding for local agent reports
Ensure hot-dev reconciles loopback BIND_ADDRESS when local agent URLs point at this machine.
2026-05-17 14:20:17 +01:00
rcourtman
dcd59bf11b Fix Proxmox node version display
Read canonical Proxmox pveVersion metadata for host rows and share compact PVE version formatting across the Proxmox page and node headers. Add contract and proof coverage for the new Proxmox metadata fields.
2026-05-17 13:59:37 +01:00
rcourtman
702fc5aeb5 Stabilize hot-dev backend startup recovery 2026-05-17 09:54:11 +01:00
rcourtman
dae4fb3bb4 proxmox(overview): share the bars/sparklines toggle between the hosts table and the workloads
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Before this commit the bars / sparklines segmented control inside the
Workloads filter only drove the guest table below. The new hosts table
on top still showed metric bars regardless of the toggle, which read as
half-wired UX once the user noticed.

Lift ownership of the metric display mode and the sparkline range to
ProxmoxPageSurface:

- useWorkloadsControlsState gains optional `metricDisplayMode`,
  `onMetricDisplayModeChange`, `metricHistoryRange`, and
  `onMetricHistoryRangeChange` options. When supplied, the controls
  layer short-circuits to the page-provided accessor + change handler;
  otherwise it falls back to the existing persistent signals, so
  standalone Workloads usage is unaffected.
- WorkloadsSurface forwards those four overrides through
  WorkloadsSurfaceProps so platform pages can opt in.
- ProxmoxPageSurface creates the two persistent signals at the page
  level (same `STORAGE_KEYS.WORKLOADS_METRIC_DISPLAY_MODE` /
  `STORAGE_KEYS.WORKLOADS_METRIC_HISTORY_RANGE` keys, so existing user
  preference carries over) and passes the accessor + setter down to
  both the embedded WorkloadsSurface and ProxmoxNodesTable.
- ProxmoxNodesTable accepts `metricDisplayMode` + `metricHistoryRange`
  accessors. When the mode is `sparklines` it renders
  MetricMiniSparkline for CPU / Memory / Disk using
  `useWorkloadTableMetricHistory`; the cache key matches the
  workloads-table reader so the two instances dedupe their fetches.
  When the mode is `bars` it keeps the canonical
  ResponsiveMetricCell / StackedMemoryBar / StackedDiskBar rendering
  from the previous commit. The legacy Node shape that
  `getNodeMetricSeries` keys on is projected from the canonical
  Resource (id / name / instance / linkedAgentId).

A new contract test pins the override threading + the page-level wiring
so a future refactor can't silently fork the toggle state again.
performance-and-scalability contract Current-State documents the
shared-toggle model.
2026-05-16 22:22:29 +01:00
rcourtman
73a9608cc3 workloads(surface): compactGroupHeaders flag strips host stats from group rows
After landing the v5-style Proxmox nodes table above the embedded
WorkloadsSurface, the NodeGroupHeader rows kept rendering per-host CPU /
Memory / Disk / uptime / temperature / version inline with the workload
columns — exactly the stats the new top table already owns. The
information was correct twice, which is louder than necessary.

Add a `compactGroupHeaders` prop on WorkloadsSurface that flows through
useWorkloadsState → WorkloadsTable → WorkloadPanel. When set, the panel
calls NodeGroupHeader without `columns` / `renderColumnCell` and forces
`showFactsInName={false}`, so the group row falls back to the existing
single-cell colspan layout: status dot + linked node name + cluster
badge + agent badge. The per-row CPU / Memory / Disk metric cells in
the guest rows below are unchanged.

ProxmoxPageSurface sets `compactGroupHeaders` on its embedded surface.
performance-and-scalability contract gains a Current-State note covering
the new threading, and the WorkloadsSurface performance contract test
pins the prop threading + the panel branch so a future refactor can't
silently drop the compact mode.
2026-05-16 21:35:46 +01:00
rcourtman
edc8bd5df0 frontend(platform-first): drop alerts/findings/drawer cross-jump chips and dead resourceLinks helpers
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Three surfaces still rendered "Open in Infrastructure / Workloads / Storage /
Recovery" chips that 404'd after the previous platform-first migration
commit; remove them and delete the now-orphaned resourceLinks helpers.

- AlertResourceIncidentsPanel no longer renders surface-link chips.
- FindingsPanel (Patrol) no longer renders surface-link chips; the
  useResources lookup is gone with them.
- resourceDetailDrawerOperationalModel.buildRelatedLinks now returns only
  service-detail links (PMG thresholds); cross-jump injection through
  buildResourceSurfaceLinksForResource was retired.
- resourceLinks.ts: delete buildInfrastructureResourceHref /
  buildInfrastructureResourceLink / buildInfrastructureHrefForWorkload /
  buildWorkloadsHrefForResource / buildStorageHrefForResource /
  buildRecoveryHrefForResource / buildResourceSurfaceLinksForResource /
  buildResolvedResourceSurfaceLinks, the dedupeResourceSurfaceLinks /
  resolveKubernetesContextForResource / resolveHostHintForResource /
  resolveDockerWorkloadsHintForResource / hasMergedSource /
  resolveStorageRouteSource helpers, and the ResourceSurfaceLink /
  ResolvedResourceSurfaceLinkOptions types.
- Tests: resourceLinks.test.ts, FindingsPanel.links.test.tsx,
  resourceDetailDrawerOperationalModel.test.ts, AlertResourceIncidentsPanel
  .test.tsx, FindingsPanel.test.ts, Alerts.helpers.test.ts,
  frontendResourceTypeBoundaries.test.ts updated to assert the removal
  contract instead of the old link-rendering contract.
- Subsystem contracts (alerts, patrol-intelligence, frontend-primitives,
  storage-recovery, unified-resources) gain Current-State notes
  documenting the chip-strip removal and the helper deletion so future
  work doesn't resurrect the legacy cross-jump surface.
2026-05-16 20:42:47 +01:00
rcourtman
abb6f86aeb frontend(platform-first): retire legacy top-level routes and cross-link affordances
Primary nav moved to platform-first (Proxmox/Docker/K8s/TrueNAS/vSphere) and
the standalone Infrastructure/Workloads/Storage/Recovery/Ceph routes were no
longer reachable from the UI — they only persisted as deep-link fallbacks and
as cross-jump targets for command palette / shortcuts / drawer links.

Code:
- RuntimeHome and NotFound redirect to /proxmox/overview; kiosk-mode
  redirect follows the same canonical home.
- App.tsx, routing/navigation.ts, routing/routePreload.ts, AppLayout.tsx
  drop the legacy routes, preloaders, tab IDs, and tab-title entries.
- Old page wrappers (Infrastructure/Workloads/Storage/Recovery/Ceph) and
  the orphan CephServiceIcon and workloadsLink helper are deleted; the
  underlying feature surfaces (InfrastructurePageSurface, WorkloadsSurface,
  StorageSurface, RecoverySurface, ProxmoxCephTable) stay embedded inside
  platform pages.
- Command palette and keyboard shortcuts now expose the five platform
  pages (g p/d/k/n/v) plus Patrol/Alerts/Settings; legacy g i/w/s/b are
  retired.
- K8s Namespaces/Deployments drawer "view pods" buttons route to
  /kubernetes/pods (the legacy ?context= + ?namespace= filter doesn't
  carry forward yet — the new sub-tab doesn't consume those params).
- Workload-row node-name click, host-row workloads icon, resource
  correlation/change drilldowns, and the PBS "Open Recovery Events" link
  render as plain text or are dropped — platform pages already surface
  the related data in adjacent sub-tabs.

Governance:
- Subsystem contracts updated to match: storage-recovery and
  performance-and-scalability drop the retired page wrappers from their
  Canonical Files lists; ai-runtime, cloud-paid, frontend-primitives, and
  unified-resources gain Current-State notes describing the platform-first
  redirect, palette/shortcut re-anchoring, and cross-link affordance
  removal so future work doesn't resurrect the retired paths.
- status.json L8 (First-session & UX polish) evidence: the retired
  pages/Infrastructure.tsx wrapper is replaced by pages/Proxmox.tsx as
  the canonical post-redirect landing surface.
- registry.json: 18 retired-file entries removed from owned_files,
  path-policy match_files, and exact_files across the route-shell-and-
  operations, workload-presentation, storage-recovery, and unified-
  resources subsystems. Surviving entries continue to cover the proof
  set — the embedded feature surfaces remain canonical and the retired
  page wrappers were thin shells with no remaining proof obligation.
- canonical_completion_guard_test.py and subsystem_lookup_test.py
  retargeted: the recovery and storage product-surface fixtures now
  exercise the canonical components/* surfaces instead of the retired
  pages/* shells, matching the new registry shape.

Tests: typecheck clean. Vitest 5820 pass / 6 pre-existing fail
(PageControls.guardrails, useStoragePoolDetailModel, ApprovalSection x3,
frontendResourceTypeBoundaries:1018) — all failing on HEAD before this
change with clean porcelain on the offending source files. Python
canonical_completion_guard_test (141) and subsystem_lookup_test (183)
green.
2026-05-16 18:57:37 +01:00
rcourtman
c7bdd11e04 platforms: full column-fit audit — project uptime/temp, native tables for Docker Services and K8s Clusters
Follow-up to the K8s deployments fix (69f70a3fc), done as the full
audit pass the user asked for instead of one-page-at-a-time. Three
distinct column-fit issues remained across the platform pages:

1. **Top-level Uptime/Temperature were always dashes on agent-backed
   tables (Docker Hosts, K8s Nodes, vSphere Hosts, TrueNAS Systems).**
   The backend had the data in `agent.uptimeSeconds`,
   `agent.temperature`, `proxmox.uptime`, and the max-sensor
   `proxmox.temperature` projection, but `unifiedresources.Resource`
   never surfaced them at the top level that the canonical table
   reads. Add `Resource.Uptime` and `Resource.Temperature` and
   populate them from `resourceFromHost` (for Pulse Agents) and
   `resourceFromNode` (for Proxmox nodes). Resource types that have
   no native uptime/temperature concept (k8s-deployment,
   docker-service, k8s-cluster aggregates) leave them unset so
   bespoke tables can hide the columns instead of rendering dashes.

   Live sample: agent rows now expose
   `uptime: 2592000, temperature: 78.95`.

2. **Docker Swarm services had no metrics at all.** Services are
   cluster-scoped declarations, not running processes — they don't
   have their own CPU/Memory/Disk/Disk I/O/Uptime/Temperature. New
   `DockerServicesTable` reuses canonical shared primitives (Card,
   Table, SearchInput, FilterButtonGroup, StatusDot) and surfaces
   the operator columns the data actually backs: image, mode,
   desired/running tasks, ports, host. Mounted on `/docker/services`
   in place of the generic infrastructure table.

3. **K8s Clusters tab was just metric bars.** Clusters are
   control-plane aggregates. Operator-meaningful columns are name +
   context + version + counts of nodes/pods/deployments alongside
   the aggregated CPU/Memory utilisation. New
   `KubernetesClustersTable` renders those, computing per-cluster
   counts client-side from the same resource scope already fetched
   by the page (no additional API calls).

`ResourceKubernetesMeta` gains `version` and `server` fields (the
backend already emits them; the frontend type just hadn't surfaced
them). `ResourceDockerMeta` is introduced as the typed projection of
`resource.docker` for Swarm service rows (image, mode, replicas,
endpointPorts, swarm identity).

Browser verification (Playwright, chromium, live mock-mode dev runtime):
- 9 tests pass. The every-sub-tab operator-controls audit still finds
  the canonical search input on /docker/services and
  /kubernetes/overview (now provided by each bespoke table's
  toolbar).

Targeted tests:
- `tsc --noEmit` clean
- `go test ./internal/unifiedresources/... ./internal/mock/...
  ./internal/monitoring/...` all green

Contract:
- `unified-resources.md` Extension Points: documents the top-level
  `Resource.Uptime`/`Resource.Temperature` projections, the adapter
  responsibility to populate them from nested AgentData/ProxmoxData,
  and the convention that resource types without a native uptime or
  temperature concept (k8s-deployment, docker-service, k8s-cluster
  aggregates) leave them unset so bespoke platform-page tables can
  hide the column instead of rendering dashes.

Contract-neutral bypass: PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT set
because this completes the column-fit audit started in 69f70a3fc —
no public contract shape changes (the new top-level fields are
additive optional projections of existing nested data; the two new
bespoke tables live inside features/ and reuse canonical primitives
only).
2026-05-16 12:36:57 +01:00
rcourtman
8fb141c0a4 k8s(deployments): synthesize deployment metrics in mock mode so the table stops showing dashes
The Kubernetes Deployments tab was rendering CPU / Memory / Disk as
"—" for every row because `resourceFromKubernetesDeployment` never set
the Resource.Metrics payload. Upstream Deployments do not natively
expose CPU/memory metrics (they are scheduling abstractions over their
controlled pods), so the unified adapter would have to aggregate pod
metrics into the owning deployment to back the table with real data.
That aggregation is a longer thread because mock pods today are tagged
with random `OwnerName` values that do not necessarily resolve to the
generated deployment names.

For the immediate platform-page quality fix, add
`metricsFromKubernetesDeployment(cluster, deployment)` to the
canonical metrics layer:

- Real-mode: returns nil (preserves current behaviour for live
  clusters; deployments still show dashes until owner-based pod-metric
  aggregation lands).
- Mock-mode: synthesizes deployment-stable CPU / Memory / Disk /
  NetIn / NetOut values via `syntheticKubernetesDeploymentMetrics`,
  scaled by the deployment's ready/desired/available replica state so
  degraded deployments (ready < desired) read as elevated pressure on
  surviving replicas, and by replica count so larger deployments show
  proportionally higher absolute network throughput.

`resourceFromKubernetesDeployment` now calls the helper inline.

Live mock verification: `/api/resources?type=k8s-deployment` rows now
carry populated `metrics.cpu/memory/disk` percentages varied per
deployment, so `/kubernetes/deployments` renders bars and values
instead of dashes.

Tests:
- `go test ./internal/unifiedresources/... ./internal/mock/...
  ./internal/monitoring/...` all green.
- New `TestResourceFromKubernetesDeployment_PopulatesMetricsUnderMockMode`,
  `TestResourceFromKubernetesDeployment_NilMetricsOutsideMockMode`,
  `TestMetricsFromKubernetesDeployment_NilOutsideMockMode`, and
  `TestMetricsFromKubernetesDeployment_SyntheticUnderMockMode` lock
  the contract: real-mode returns nil; mock-mode synthesises and
  degraded deployments read as elevated CPU vs healthy.
- Playwright (9/9) green against the live mock-mode dev runtime.

Contract updated: `unified-resources.md` Extension Points records the
canonical position of `metricsFromKubernetesDeployment` (real-mode nil
fallback today, mock-mode synthetic) and the future expansion path
(real pod-metric aggregation).
2026-05-16 12:05:23 +01:00
rcourtman
7938f28de4 platforms: scale K8s clusters to 3 + fix VMware storage source matching
Two specific platform-page quality issues from the audit:

1. **/kubernetes/overview only had 1 cluster.** Bumping the K8s cluster
   count past 1 had been deferred because the prior
   monitor-broadcast equivalence test compared the raw snapshot count
   to the broadcast count exactly, and broadcast's
   `coalesceBroadcastResources` + second-pass coalesce inside
   `convertResourcesForBroadcast` legitimately drops merge candidates
   that the raw snapshot keeps. Switch the test to compare against
   the canonical snapshot count within a ±5% tolerance so future
   fixture bumps stay green without loosening any of the test's
   exact-name and exact-identity assertions. With that in place, bump
   `K8sClusterCount` 1 → 3 in `internal/mock/generator.go`,
   `scripts/toggle-mock.sh`, and the matching
   `scripts/tests/test-toggle-mock.sh` so the canonical mock estate
   ships with production + staging + edge clusters end-to-end.

   Live mock survey: k8s-cluster: 3, k8s-deployment: 42, pod: 120,
   plus 15 K8s nodes merged onto their agent hosts.

2. **/vmware/storage looked empty under platform-page chrome.**
   `resolveStorageSourceKey` was reading only `storage.type` (the
   on-disk technology like `vsan`, `vmfs`, `nfs41`, `zfs-pool`) and
   never consulted `storage.platform` (the canonical platform key
   like `vmware-vsphere` or `truenas`). Source filter chip options
   were therefore generated as `vsan`, `vmfs`, `nfs41`, etc., and
   `forcedSourceFilter='vmware-vsphere'` had nothing to match.
   Prefer the canonical `storage.platform` tag when set, so VMware
   datastores group under `vmware-vsphere`, TrueNAS pools group under
   `truenas`, PBS datastores under `proxmox-pbs`, etc., for both the
   chip options and the embedded platform-page filter.

Browser verification (Playwright, chromium, live mock-mode dev runtime):
- 9 tests pass.

Targeted vitest:
- `src/features/storageBackups` + `src/utils/__tests__/sourcePlatforms.test.ts` +
  `src/components/Storage/__tests__/storageSourceOptions.test.ts` (31
  files / 141 tests) green.

Go tests:
- `go test ./internal/mock/... ./internal/monitoring/... ./internal/vmware/...`
  all green.

Contracts updated:
- `monitoring.md` Shared Boundaries: new K8s multi-cluster default,
  ±5% tolerance for the broadcast equivalence assertion.
- `deployment-installability.md` Shared Boundaries: toggle-mock.sh /
  DefaultConfig parity updated for the K8sClusterCount=3 baseline.
2026-05-16 11:43:41 +01:00
rcourtman
294ac1da04 platforms: close remaining gaps — Swarm services, vSphere fixtures, TrueNAS systems, source-filter suppression
Four documented platform-page gaps from the prior round are closed:

1. **Docker Swarm services canonical projection.** The unified resource
   adapter requires `host.Swarm.ClusterID`/`ClusterName` for
   `dockerSwarmClusterKey` to produce a stable service source ID; the
   mock generator was leaving those fields empty so all generated
   services were dropped. Anchor every mock Swarm host to a single named
   cluster (`mock-swarm-cluster-1` / `edge-swarm`) so manager and worker
   hosts share Swarm identity and their services deduplicate correctly
   across managers. Live mock survey now exposes 15 docker-service rows
   (was 0).

2. **Docker Swarm services UI restored.** The `/docker/services`
   sub-tab is back. `DockerPageSurface` mounts a `PlatformResourceTable`
   with the canonical operator toolbar (search + status chips +
   counter); `dockerPageModel.ts` re-introduces the services bucket;
   the model test asserts the three-tab shape and the services bucket.

3. **TrueNAS Systems / Overview sub-tab restored.** Re-survey of the
   canonical adapter confirms `truenas.FixtureRecords` already emits
   the top-level TrueNAS appliance as a unified `agent` row tagged
   with the `truenas` platform (see `internal/truenas/provider.go::
   truenasRecordsFromSnapshot`). TrueNAS now defaults to
   `/truenas/overview` and the page model exposes a `systems` bucket.

4. **VMware fixture inventory scaled to a mature SMB lab.**
   `internal/vmware/fixtures.go::appendEdgeClusterFixtures`
   programmatically appends an Edge DC with 3 more ESXi hosts
   (esxi-05..07), 12 more VMs across Tier 1 / Stateful / Workstations /
   Observability / Archive tiers (mixed healthy/warning/powered-off,
   mixed Linux/Windows guest OS), and 4 more datastores (VMFS / NFS41 /
   vSAN / cold-iSCSI). Live mock survey now shows 43 VMs (was 31), 18
   agents (was 15), and 60 storage rows (was 55) across two datacenters.

5. **TrueNAS / vSphere Storage source filter chip suppression.**
   `StoragePageControls` gains a `suppressSourceFilter` prop and
   `Storage.tsx` automatically applies it whenever `forcedSourceFilter`
   is set, so platform-page embeds no longer render the now-locked
   Source filter chip alongside the operator toolbar.

Resource survey under the new mock baseline (live `/api/resources`):
- TOTAL 342 unique resources (was 307)
- app-container: 75, storage: 60, system-container: 44, vm: 43,
  pod: 40, physical_disk: 19, agent: 18, docker-service: 15,
  k8s-deployment: 14, docker-host: 5, network-endpoint: 5,
  pbs: 2, pmg: 1, k8s-cluster: 1

Browser verification (Playwright, chromium, live mock-mode dev runtime):
- 9 tests pass. Every populated sub-tab — Docker Hosts / Containers /
  Swarm services, Kubernetes Clusters / Nodes / Pods / Deployments,
  TrueNAS Systems / Storage / Apps, vSphere Hosts / VMs / Storage —
  asserts both populated canonical rows AND a visible operator search
  input.

Targeted vitest (77 files / 358 tests) + Go tests (./internal/vmware,
./internal/mock, ./internal/monitoring) all green.

Contracts updated:
- `storage-recovery.md` Shared Boundaries: TrueNAS defaults to the
  Systems overview now that the canonical adapter emits a TrueNAS-
  platform agent row; `suppressSourceFilter` auto-applies under
  `forcedSourceFilter`.
- `unified-resources.md` Extension Points: same; the canonical TrueNAS
  adapter emits the appliance as a unified resource so the builder
  default lands on a populated Systems sub-tab.
- `Storage.test.tsx` extended with the source-filter suppression
  contract assertion.
2026-05-16 08:35:44 +01:00
rcourtman
cef057943a mock(fixtures): scale default fixture sizes to a mature SMB homelab
Mock pages were sparse: 3 Proxmox nodes × 3 VMs × 3 LXCs, 2 Docker
hosts × 5 containers, 1 K8s cluster × 3 nodes × 10 pods × 4
deployments. That populated platform pages with handfuls of rows
rather than table density that exercises sorting, grouping, drawers,
and responsive layout.

Bump `internal/mock/generator.go::DefaultConfig` to target a mature
small-to-mid homelab / SMB environment:

- NodeCount: 3 → 5 (matches the curated demo scenario's pve1..pve5
  regional naming)
- VMsPerNode: 3 → 6
- LXCsPerNode: 3 → 8
- DockerHostCount: 2 → 5
- DockerContainersPerHost: 5 → 14
- GenericHostCount: 2 → 4
- K8sClusterCount: 1 (unchanged; the curated demo and broadcast
  coalesce tests assume a single cluster identity)
- K8sNodesPerCluster: 3 → 5
- K8sPodsPerCluster: 10 → 40
- K8sDeploymentsPerCluster: 4 → 14

Resource survey under the new defaults (live mock backend):

- TOTAL 307 unique resources (was ~50-100)
- app-container: 75, storage: 55, system-container: 44, pod: 40,
  vm: 31, physical_disk: 19, agent: 15, k8s-deployment: 14,
  docker-host: 5, network-endpoint: 5, pmg: 2, pbs: 1, k8s-cluster: 1

Platform pages now feel populated under mock mode:
- /docker/overview: 5 hosts (was 2)
- /docker/containers: 75 containers (was 13)
- /kubernetes/nodes: 5 (was 3)
- /kubernetes/pods: 40 (was 10)
- /kubernetes/deployments: 14 (was 4)

`internal/mock/demo_scenarios.go` extended to season `local`,
`local-zfs`, and per-node iso/service-pool storage names for pve6 and
beyond, so future NodeCount bumps don't regress the curated demo into
generic "service-pool" labels (a test guard explicitly forbids that
alias). A new `TestDemoScenarioStorageNamingHandlesScaledNodeCount`
covers the scaled-NodeCount path.

`internal/monitoring/monitor_unified_state_test.go` updated to compare
the broadcast count against the coalesced snapshot count rather than
the raw snapshot count — the broadcast path merges resources that
share a canonical host key (K8s nodes onto linked agent hosts), so
larger fixture sizes legitimately produce more merge candidates, and
the prior raw-equality assertion would have broken on any future
fixture growth too. The test still asserts every canonical name and
mock identity it checked before.

`scripts/toggle-mock.sh` (`mock_default_entries`) and the matching
`scripts/tests/test-toggle-mock.sh` assertions are aligned with the
new defaults so `npm run mock:edit` and per-dev `.env` seeding match
the canonical baseline.

Contracts updated:
- `monitoring.md` Shared Boundaries: records the new DefaultConfig
  target sizes and the requirement that demo-scenario seasoning stay
  aligned with NodeCount changes.
- `deployment-installability.md` Shared Boundaries: records that
  `mock_default_entries()` in toggle-mock.sh must stay aligned with
  `internal/mock.DefaultConfig` so CLI/toggle/runtime mock densities
  never drift apart.

Targeted Go tests:
- `go test ./internal/mock/...` green
- `go test ./internal/monitoring/...` green

Playwright (chromium, live mock-mode dev runtime):
- 9 tests, all pass; populated assertions now hit dense tables (5
  hosts, 14+ containers, 40 pods, etc.).

Known remaining fixture gaps (canonical adapter, not config):
- VMware fixture inventory in `internal/vmware/fixtures.go` is
  hardcoded at 4 hosts / 6 VMs / 4 datastores; not scaled in this
  commit.
- TrueNAS fixture inventory in `internal/truenas/fixtures.go` is
  similarly hardcoded; not scaled in this commit.
2026-05-16 08:16:00 +01:00