From 7652e2833ea43bb5273080d322166182c8ed8d14 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 19 May 2026 17:27:06 +0100 Subject: [PATCH] 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. --- .../internal/PLATFORM_SUPPORT_MANIFEST.json | 7 +-- .../v6/internal/PLATFORM_SUPPORT_MODEL.md | 27 ++++++----- .../src/features/vmware/VmwarePageSurface.tsx | 2 +- ...frastructureOnboardingPresentation.test.ts | 20 ++++---- .../utils/__tests__/sourcePlatforms.test.ts | 4 +- .../platformSupportManifest.generated.ts | 15 +++--- .../mock/platform_support_contract_test.go | 46 +++++++++++++------ 7 files changed, 71 insertions(+), 50 deletions(-) diff --git a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json index 95337f59a..d807df764 100644 --- a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json +++ b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json @@ -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" diff --git a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md index 19d425cf3..827ac9604 100644 --- a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md +++ b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md @@ -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 diff --git a/frontend-modern/src/features/vmware/VmwarePageSurface.tsx b/frontend-modern/src/features/vmware/VmwarePageSurface.tsx index 17466bf98..42b80ca83 100644 --- a/frontend-modern/src/features/vmware/VmwarePageSurface.tsx +++ b/frontend-modern/src/features/vmware/VmwarePageSurface.tsx @@ -68,7 +68,7 @@ export function VmwarePageSurface() { } > diff --git a/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts b/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts index fce919dba..68a8fd266 100644 --- a/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts @@ -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', diff --git a/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts b/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts index e35a3cad6..01d3cc5f2 100644 --- a/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts +++ b/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts @@ -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', diff --git a/frontend-modern/src/utils/platformSupportManifest.generated.ts b/frontend-modern/src/utils/platformSupportManifest.generated.ts index bcdd9f049..5bbf0563a 100644 --- a/frontend-modern/src/utils/platformSupportManifest.generated.ts +++ b/frontend-modern/src/utils/platformSupportManifest.generated.ts @@ -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', diff --git a/internal/mock/platform_support_contract_test.go b/internal/mock/platform_support_contract_test.go index f0fc637fa..7549b7001 100644 --- a/internal/mock/platform_support_contract_test.go +++ b/internal/mock/platform_support_contract_test.go @@ -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,