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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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).
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.
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.
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.