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.
This commit is contained in:
rcourtman 2026-05-19 17:27:06 +01:00
parent 073634bcef
commit 7652e2833e
7 changed files with 71 additions and 50 deletions

View file

@ -7,7 +7,8 @@
"proxmox-pbs",
"proxmox-pmg",
"docker",
"kubernetes"
"kubernetes",
"vmware-vsphere"
],
"agent_host_profiles": [
{
@ -279,8 +280,8 @@
{
"id": "vmware-vsphere",
"family": "VMware",
"governance_state": "admitted",
"readiness_stage": "first-lab-ready",
"governance_state": "supported",
"readiness_stage": "supported",
"primary_mode": "api-backed",
"onboarding_paths": [
"platform-connections"

View file

@ -242,10 +242,11 @@ Rules:
5. `proxmox-pbs`
6. `proxmox-pmg`
7. `truenas`
8. `vmware-vsphere`
### Admitted platforms (not yet supported)
1. `vmware-vsphere`
(none)
### Presentation-only platform vocabulary
@ -285,7 +286,7 @@ Support floor fields are recorded in this order: `setup`, `visibility`,
| `proxmox-pbs` | `supported` | `supported` | `api-backed` | `platform-connections` | `pbs`, `storage` | `setup=supported`; `visibility=supported`; `workloads=n/a`; `storage=supported`; `recovery=supported`; `alerts=supported`; `assistant_read=supported`; `assistant_control=read-only` |
| `proxmox-pmg` | `supported` | `supported` | `api-backed` | `platform-connections` | `pmg` | `setup=supported`; `visibility=supported`; `workloads=n/a`; `storage=n/a`; `recovery=n/a`; `alerts=supported`; `assistant_read=supported`; `assistant_control=read-only` |
| `truenas` | `supported` | `supported` | `api-backed` | `platform-connections` | `agent`, `app-container`, `storage`, `physical-disk` | `setup=supported`; `visibility=supported`; `workloads=supported`; `storage=supported`; `recovery=supported`; `alerts=supported`; `assistant_read=supported`; `assistant_control=supported` |
| `vmware-vsphere` | `admitted` | `first-lab-ready` | `api-backed` | `platform-connections` | `agent`, `vm`, `storage` | `setup=supported`; `visibility=supported`; `workloads=supported`; `storage=supported`; `recovery=n/a`; `alerts=supported`; `assistant_read=supported`; `assistant_control=read-only` |
| `vmware-vsphere` | `supported` | `supported` | `api-backed` | `platform-connections` | `agent`, `vm`, `storage` | `setup=supported`; `visibility=supported`; `workloads=supported`; `storage=supported`; `recovery=n/a`; `alerts=supported`; `assistant_read=supported`; `assistant_control=read-only` |
Presentation-only platform vocabulary uses `governance=presentation-only`,
`readiness=presentation-only`, `primary_mode=presentation-only`, no onboarding
@ -343,17 +344,19 @@ platform fallbacks that are not declared in this document.
| `proxmox-pve` | Proxmox | api-backed | host agent may augment into hybrid | `agent`, `vm`, `system-container`, `storage`, `ceph`, `physical-disk` |
| `proxmox-pbs` | Proxmox | api-backed | host agent may augment into hybrid | `pbs`, `storage` |
| `proxmox-pmg` | Proxmox | api-backed | none today | `pmg` |
| `truenas` | TrueNAS | api-backed | host agent may augment into hybrid | `agent`, `app-container`, `storage`, `physical-disk` |
| `truenas` | TrueNAS | api-backed | host agent may augment into hybrid | `agent`, `app-container`, `storage`, `physical-disk` |
| `vmware-vsphere` | VMware | api-backed | host or guest agent later, not phase 1 | `agent`, `vm`, `storage` |
| Platform | Setup | Visibility | Workloads | Storage | Recovery | Alerts | Assistant read | Assistant control |
| ------------- | -------------------------------------- | ---------- | --------- | --------- | --------- | --------- | -------------- | ----------------- |
| `agent` | install workspace | supported | `n/a` | supported | `n/a` | supported | supported | supported |
| `docker` | install workspace / runtime enablement | supported | supported | `n/a` | `n/a` | supported | supported | supported |
| `kubernetes` | install workspace / runtime enablement | supported | supported | `n/a` | supported | supported | supported | supported |
| `proxmox-pve` | platform connections | supported | supported | supported | supported | supported | supported | augmentation-only |
| `proxmox-pbs` | platform connections | supported | `n/a` | supported | supported | supported | supported | read-only |
| `proxmox-pmg` | platform connections | supported | `n/a` | `n/a` | `n/a` | supported | supported | read-only |
| `truenas` | platform connections | supported | supported | supported | supported | supported | supported | supported |
| Platform | Setup | Visibility | Workloads | Storage | Recovery | Alerts | Assistant read | Assistant control |
| ---------------- | -------------------------------------- | ---------- | --------- | --------- | --------- | --------- | -------------- | ----------------- |
| `agent` | install workspace | supported | `n/a` | supported | `n/a` | supported | supported | supported |
| `docker` | install workspace / runtime enablement | supported | supported | `n/a` | `n/a` | supported | supported | supported |
| `kubernetes` | install workspace / runtime enablement | supported | supported | `n/a` | supported | supported | supported | supported |
| `proxmox-pve` | platform connections | supported | supported | supported | supported | supported | supported | augmentation-only |
| `proxmox-pbs` | platform connections | supported | `n/a` | supported | supported | supported | supported | read-only |
| `proxmox-pmg` | platform connections | supported | `n/a` | `n/a` | `n/a` | supported | supported | read-only |
| `truenas` | platform connections | supported | supported | supported | supported | supported | supported | supported |
| `vmware-vsphere` | platform connections | supported | supported | supported | `n/a` | supported | supported | read-only |
## Current Inconsistencies To Treat Explicitly

View file

@ -68,7 +68,7 @@ export function VmwarePageSurface() {
<PlatformTableEmptyState
icon={vmwareIcon()}
title="No vSphere hosts"
description="VMware vSphere is in first-lab-ready readiness. Add a vCenter connection in Settings to populate this page."
description="Add a vCenter connection in Settings to populate this page."
/>
}
>

View file

@ -17,12 +17,12 @@ import {
} from '@/utils/infrastructureOnboardingPresentation';
describe('infrastructureOnboardingPresentation', () => {
it('keeps VMware on the admitted vCenter-only path', () => {
it('keeps VMware on the supported vCenter path', () => {
const vmware = getInfrastructureOnboardingProductPresentation('vmware');
expect(vmware.label).toBe('VMware vCenter');
expect(vmware.governanceState).toBe('admitted');
expect(vmware.readinessStage).toBe('first-lab-ready');
expect(vmware.governanceState).toBe('supported');
expect(vmware.readinessStage).toBe('supported');
expect(vmware.primaryMode).toBe('api-backed');
expect(vmware.canonicalProjections).toEqual(['agent', 'vm', 'storage']);
expect(vmware.supportFloor).toMatchObject({
@ -87,10 +87,11 @@ describe('infrastructureOnboardingPresentation', () => {
expect(INFRASTRUCTURE_ONBOARDING_PATHS.agent.title).toBe('Install Pulse Agent');
});
it('keeps supported API products separate from the admitted VMware path', () => {
it('lists all supported API products alongside vSphere now that VMware is promoted', () => {
expect(
getInfrastructureApiProductsByGovernanceState('supported').map((product) => product.label),
).toEqual([
'VMware vCenter',
'TrueNAS SCALE',
'Proxmox VE',
'Proxmox Backup Server',
@ -100,13 +101,7 @@ describe('infrastructureOnboardingPresentation', () => {
expect(
getInfrastructureApiProductsByGovernanceState('admitted').map((product) => product.label),
).toEqual(['VMware vCenter']);
expect(
getInfrastructureGovernanceBadgeLabel(
getInfrastructureOnboardingProductPresentation('vmware').governanceState,
getInfrastructureOnboardingProductPresentation('vmware').readinessStage,
),
).toBe('Early support');
).toEqual([]);
expect(getInfrastructureGovernanceBadgeLabel('supported', 'supported')).toBeNull();
});
@ -209,6 +204,7 @@ describe('infrastructureOnboardingPresentation', () => {
expect(getInfrastructureSupportSummaryBadges()).toMatchObject({
supportedToday: [
'VMware vCenter',
'TrueNAS SCALE',
'Proxmox VE',
'Proxmox Backup Server',
@ -218,7 +214,7 @@ describe('infrastructureOnboardingPresentation', () => {
'Docker',
'Kubernetes',
],
currentAdmissionPath: ['VMware vCenter'],
currentAdmissionPath: [],
installPath: expect.arrayContaining([
'Linux',
'FreeBSD',

View file

@ -140,8 +140,8 @@ describe('sourcePlatforms', () => {
});
describe('governed platform support projection', () => {
it('keeps admitted VMware on the first-lab-ready support floor', () => {
expect(getSourcePlatformReadinessStage('vmware')).toBe('first-lab-ready');
it('keeps VMware on the supported vCenter-backed floor', () => {
expect(getSourcePlatformReadinessStage('vmware')).toBe('supported');
expect(getSourcePlatformCanonicalProjections('vmware')).toEqual(['agent', 'vm', 'storage']);
expect(getSourcePlatformSupportFloor('vmware')).toMatchObject({
setup: 'supported',

View file

@ -1,11 +1,11 @@
// This file is generated by scripts/release_control/generate_platform_support_frontend_module.py.
// Do not edit by hand.
// Source: docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json
// Source SHA256: 7126ef35ec2c2cda7e04b4a5496337d9d194926f7586af89fb3ae082fc7d6a2e
// Source SHA256: a24d731a8717835071e48d58b02c8cc0ea6e40e85cf9fd8e2d26733397b44be5
export const PLATFORM_SUPPORT_MANIFEST_SOURCE = {
path: 'docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json',
sha256: '7126ef35ec2c2cda7e04b4a5496337d9d194926f7586af89fb3ae082fc7d6a2e',
sha256: 'a24d731a8717835071e48d58b02c8cc0ea6e40e85cf9fd8e2d26733397b44be5',
} as const;
export const PLATFORM_SUPPORT_MANIFEST = {
schemaVersion: 1,
@ -17,6 +17,7 @@ export const PLATFORM_SUPPORT_MANIFEST = {
'proxmox-pmg',
'docker',
'kubernetes',
'vmware-vsphere',
],
agentHostProfiles: [
{
@ -211,8 +212,8 @@ export const PLATFORM_SUPPORT_MANIFEST = {
{
id: 'vmware-vsphere',
family: 'VMware',
governanceState: 'admitted',
readinessStage: 'first-lab-ready',
governanceState: 'supported',
readinessStage: 'supported',
primaryMode: 'api-backed',
onboardingPaths: ['platform-connections'],
canonicalProjections: ['agent', 'vm', 'storage'],
@ -390,8 +391,9 @@ export const SUPPORTED_PLATFORM_IDS = [
'proxmox-pbs',
'proxmox-pmg',
'truenas',
'vmware-vsphere',
] as const;
export const ADMITTED_PLATFORM_IDS = ['vmware-vsphere'] as const;
export const ADMITTED_PLATFORM_IDS = [] as const;
export const PRESENTATION_ONLY_PLATFORM_IDS = [
'unraid',
'synology-dsm',
@ -435,6 +437,7 @@ export const DEFAULT_INFRASTRUCTURE_SOURCE_ORDER = [
'proxmox-pmg',
'docker',
'kubernetes',
'vmware-vsphere',
] as const;
export const SOURCE_PLATFORM_ALIAS_MAP = {
k8s: 'kubernetes',
@ -551,7 +554,7 @@ export const SOURCE_PLATFORM_READINESS_STAGE = {
'proxmox-pbs': 'supported',
'proxmox-pmg': 'supported',
truenas: 'supported',
'vmware-vsphere': 'first-lab-ready',
'vmware-vsphere': 'supported',
unraid: 'presentation-only',
'synology-dsm': 'presentation-only',
'microsoft-hyperv': 'presentation-only',

View file

@ -171,44 +171,41 @@ func TestMockCoverageMatchesCurrentSupportedPlatformSet(t *testing.T) {
}
}
func TestVMwareFixturesRemainAdmittedButNotSupported(t *testing.T) {
func TestVMwareFixturesRemainSupportedAtPhase1Floor(t *testing.T) {
model := loadPlatformSupportModel(t)
manifest := loadPlatformSupportManifest(t)
supported := manifestPlatformsByState(t, manifest, "supported")
admitted := manifestPlatformsByState(t, manifest, "admitted")
presentationOnly := manifestPlatformsByState(t, manifest, "presentation-only")
if containsPlatform(supported, "vmware-vsphere") {
t.Fatal("vmware-vsphere must not appear in the current supported platform set before live proof admits it")
if !containsPlatform(supported, "vmware-vsphere") {
t.Fatal("vmware-vsphere must appear in the current supported platform set after promotion from first-lab-ready")
}
if !containsPlatform(admitted, "vmware-vsphere") {
t.Fatal("expected vmware-vsphere to remain admitted while it is outside the supported platform set")
if containsPlatform(admitted, "vmware-vsphere") {
t.Fatal("vmware-vsphere must not remain in the admitted set after promotion to supported")
}
if containsPlatform(presentationOnly, "vmware-vsphere") {
t.Fatal("vmware-vsphere must not regress into presentation-only vocabulary once admitted")
t.Fatal("vmware-vsphere must not regress into presentation-only vocabulary once supported")
}
if !strings.Contains(model, "| `vmware-vsphere` |") {
t.Fatal("expected platform support model to keep the vmware-vsphere admission row")
t.Fatal("expected platform support model to keep the vmware-vsphere support row")
}
vmwareOnboardingPaths, ok := manifestPlatformOnboardingPaths(manifest)["vmware-vsphere"]
if !ok {
t.Fatal("expected vmware-vsphere onboarding-path manifest entry")
}
if diff := diffPlatformSets([]string{"platform-connections"}, vmwareOnboardingPaths); diff != "" {
t.Fatalf("vmware-vsphere onboarding path drifted from the admission model:\n%s", diff)
}
if !strings.Contains(model, "| `vmware-vsphere` | platform connections to `vCenter` only |") {
t.Fatal("expected platform support model to keep the vmware-vsphere platform-connections admission floor")
t.Fatalf("vmware-vsphere onboarding path drifted from the support model:\n%s", diff)
}
vmwareManifest := requireManifestPlatform(t, manifest, "vmware-vsphere")
if vmwareManifest.ReadinessStage != "first-lab-ready" {
t.Fatalf("vmware readiness stage = %q, want first-lab-ready", vmwareManifest.ReadinessStage)
if vmwareManifest.ReadinessStage != "supported" {
t.Fatalf("vmware readiness stage = %q, want supported", vmwareManifest.ReadinessStage)
}
if vmwareManifest.PrimaryMode != "api-backed" {
t.Fatalf("vmware primary mode = %q, want api-backed", vmwareManifest.PrimaryMode)
}
if diff := diffPlatformSets([]string{"agent", "storage", "vm"}, vmwareManifest.CanonicalProjections); diff != "" {
t.Fatalf("vmware canonical projections drifted from the admission model:\n%s", diff)
t.Fatalf("vmware canonical projections drifted from the support model:\n%s", diff)
}
if got := vmwareManifest.SupportFloor["recovery"]; got != "n/a" {
t.Fatalf("vmware recovery support floor = %q, want n/a", got)
@ -531,6 +528,10 @@ func parsePlatformListSection(t *testing.T, model string, heading string) []stri
var platforms []string
inSection := false
// The admitted-platform section is allowed to be empty once the last
// admitted platform graduates to supported; every other section must
// declare at least one platform.
allowEmpty := strings.Contains(heading, "Admitted platforms")
for _, raw := range strings.Split(model, "\n") {
line := strings.TrimSpace(raw)
@ -541,6 +542,9 @@ func parsePlatformListSection(t *testing.T, model string, heading string) []stri
case !inSection:
continue
case strings.HasPrefix(line, "### ") || strings.HasPrefix(line, "## "):
if allowEmpty {
return allowEmptyPlatformList(platforms)
}
return requireNonEmptyPlatformList(t, platforms, heading)
}
@ -550,6 +554,9 @@ func parsePlatformListSection(t *testing.T, model string, heading string) []stri
}
}
if allowEmpty {
return allowEmptyPlatformList(platforms)
}
return requireNonEmptyPlatformList(t, platforms, heading)
}
@ -630,6 +637,9 @@ func manifestPlatformsByState(
}
}
if governanceState == "admitted" {
return allowEmptyPlatformList(platforms)
}
return requireNonEmptyPlatformList(t, platforms, fmt.Sprintf("manifest platforms with state %s", governanceState))
}
@ -931,6 +941,14 @@ func requireNonEmptyPlatformList(t *testing.T, platforms []string, label string)
return unique
}
// allowEmptyPlatformList mirrors requireNonEmptyPlatformList but tolerates an
// empty result. Used for governance sets that can legitimately be empty
// (e.g. the admitted-platform staging area after the last admitted platform
// has been promoted to supported).
func allowEmptyPlatformList(platforms []string) []string {
return uniqueSortedPlatforms(platforms)
}
func requireNonEmptyPlatformFieldMap(
t *testing.T,
values map[string][]string,