diff --git a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json
index ad2ccbe2f..8acffb798 100644
--- a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json
+++ b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json
@@ -290,8 +290,8 @@
"id": "vmware-vsphere",
"family": "VMware",
"surface_kind": "platform",
- "governance_state": "supported",
- "readiness_stage": "supported",
+ "governance_state": "admitted",
+ "readiness_stage": "first-lab-ready",
"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 87bef88d9..0676d5cd6 100644
--- a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md
+++ b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md
@@ -261,7 +261,6 @@ Rules:
4. `proxmox-pbs`
5. `proxmox-pmg`
6. `truenas`
-7. `vmware-vsphere`
### Runtime lenses
@@ -269,7 +268,7 @@ Rules:
### Admitted platforms (not yet supported)
-(none)
+1. `vmware-vsphere`
### Presentation-only platform vocabulary
@@ -309,7 +308,6 @@ Support floor fields are recorded in this order: `setup`, `visibility`,
| `proxmox-pbs` | `platform` | `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` | `platform` | `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` | `platform` | `supported` | `supported` | `api-backed` | `platform-connections` | `agent`, `vm`, `app-container`, `network-share`, `storage`, `physical-disk` | `setup=supported`; `visibility=supported`; `workloads=supported`; `storage=supported`; `recovery=supported`; `alerts=supported`; `assistant_read=supported`; `assistant_control=supported` |
-| `vmware-vsphere` | `platform` | `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`,
`surface_kind=presentation-only`, `readiness=presentation-only`,
@@ -369,7 +367,6 @@ or runtime platform fallbacks that are not declared in this document.
| `proxmox-pbs` | platform | Proxmox | api-backed | host agent may augment into hybrid | `pbs`, `storage` |
| `proxmox-pmg` | platform | Proxmox | api-backed | none today | `pmg` |
| `truenas` | platform | TrueNAS | api-backed | host agent may augment into hybrid | `agent`, `vm`, `app-container`, `network-share`, `storage`, `physical-disk` |
-| `vmware-vsphere` | platform | 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 |
| ---------------- | -------------------------------------- | ---------- | --------- | --------- | --------- | --------- | -------------- | ----------------- |
@@ -380,7 +377,6 @@ or runtime platform fallbacks that are not declared in this document.
| `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
@@ -426,10 +422,10 @@ open-decision or resolved-decision update instead of starting runtime code.
## VMware vSphere Admission Model
-Pulse now has a resolved architecture recommendation for any future VMware
-vSphere work. This does not admit `vmware-vsphere` into the current support
-matrix yet. It defines the only acceptable phase-1 model if implementation
-starts.
+Pulse now has a resolved architecture recommendation for VMware vSphere work.
+This admits `vmware-vsphere` only at the `first-lab-ready` checkpoint; it does
+not place VMware in the current support matrix yet. It defines the only
+acceptable phase-1 model for implementation and proof.
1. treat VMware as one separate first-class platform id,
`vmware-vsphere`, not as another Proxmox subtype and not as a generic
diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md
index a4b595824..eb414297a 100644
--- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md
+++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md
@@ -210,7 +210,11 @@ projections, and support-floor posture.
those facts into source-strategy copy, but it must not turn an admitted
`first-lab-ready` platform such as VMware into a product-level supported
claim, invent platform-local projections, or classify assistant control beyond
-the manifest's support-floor row.
+the manifest's support-floor row. Admitted API platforms may remain available
+inside the add-flow source picker and source-manager summaries, but their
+operator-facing badge must be preview/admission language and empty-state copy
+must keep them separate from the supported-source list until the governed live
+proof promotes the platform.
That same lifecycle-owned helper must keep first-class platform APIs separate
from governed Pulse Agent host profiles. Host/appliance compatibility such as
Unraid is presented as an agent install/profile path sourced from the manifest
diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
index 3a162515e..2af709d9f 100644
--- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md
+++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
@@ -766,7 +766,7 @@ not a replacement status card, CTA band, or page-local nested card.
the container-runtime lens and may be labelled `Containers` in the shell,
while shared source badges, filters, and runtime management copy continue
to use `Docker / Podman` where the capability itself is being named.
-8. Keep shared source/platform vocabulary on the governed manifest boundary. `frontend-modern/src/utils/platformSupportManifest.generated.ts` must be the tracked frontend projection of `docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json`, `frontend-modern/src/utils/platformSupportManifest.ts`, `frontend-modern/src/utils/sourcePlatforms.ts`, and `frontend-modern/src/utils/sourcePlatformOptions.ts` must consume that generated projection instead of embedding divergent future-label lists, setup/onboarding path allowlists, host-profile labels, surface-kind guesses, or presentation-only guesses, and `frontend-modern/scripts/canonical-platform-audit.mjs` must fail when the generated projection drifts from the governed manifest. The generated `surface_kind` is the machine-readable boundary between owning platform entries and runtime lenses: `docker` is a `runtime-lens`, not a `platform`, even when the container-runtime route stays available as a primary shell destination. The generic `docker` source-platform label is "Docker / Podman" in shared selectors, badges, and filter options so v5 Docker users can find the runtime surface while Podman-backed rows are not mislabeled as Docker-only; "Container runtime" remains the governed runtime family, not the primary customer-facing label. Identity colour is semantic, not page-local decoration: shared source/platform badges, host identity badges, and container runtime badges must use the shared presentation helpers so Docker remains on the Docker/Podman blue runtime tone, Podman uses its distinct runtime tone, Proxmox PVE remains orange, and those meanings do not drift across table rows, filters, drawers, or platform pages. Agent host-profile entries, including Unraid, stay in the generated `agentHostProfiles` projection and shared wrapper helpers; frontend primitives may render those labels for Pulse Agent install/identity copy but must not add them to the first-class platform union.
+8. Keep shared source/platform vocabulary on the governed manifest boundary. `frontend-modern/src/utils/platformSupportManifest.generated.ts` must be the tracked frontend projection of `docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json`, `frontend-modern/src/utils/platformSupportManifest.ts`, `frontend-modern/src/utils/sourcePlatforms.ts`, and `frontend-modern/src/utils/sourcePlatformOptions.ts` must consume that generated projection instead of embedding divergent future-label lists, setup/onboarding path allowlists, host-profile labels, surface-kind guesses, readiness-state guesses, or presentation-only guesses, and `frontend-modern/scripts/canonical-platform-audit.mjs` must fail when the generated projection drifts from the governed manifest. The generated governance/readiness split is authoritative: supported platform arrays drive current support claims, while admitted platform arrays may keep route/navigation and add-flow vocabulary available without turning `first-lab-ready` entries such as VMware into supported-source copy. The generated `surface_kind` is the machine-readable boundary between owning platform entries and runtime lenses: `docker` is a `runtime-lens`, not a `platform`, even when the container-runtime route stays available as a primary shell destination. The generic `docker` source-platform label is "Docker / Podman" in shared selectors, badges, and filter options so v5 Docker users can find the runtime surface while Podman-backed rows are not mislabeled as Docker-only; "Container runtime" remains the governed runtime family, not the primary customer-facing label. Identity colour is semantic, not page-local decoration: shared source/platform badges, host identity badges, and container runtime badges must use the shared presentation helpers so Docker remains on the Docker/Podman blue runtime tone, Podman uses its distinct runtime tone, Proxmox PVE remains orange, and those meanings do not drift across table rows, filters, drawers, or platform pages. Agent host-profile entries, including Unraid, stay in the generated `agentHostProfiles` projection and shared wrapper helpers; frontend primitives may render those labels for Pulse Agent install/identity copy but must not add them to the first-class platform union.
The generated host-profile projection also carries runtime platform fallback
metadata for shared explanation and parity with backend normalization, but
frontend primitives must still render host-profile labels through
@@ -3316,7 +3316,10 @@ That same shared settings-shell boundary also owns provider parity inside the
inline add flow. Adding VMware may extend the same card, empty-state, dialog,
and summary-shell patterns used by TrueNAS, but it must not introduce a
VMware-only outer page shell, alternate settings route hierarchy, or another
-summary vocabulary for connection health and contribution counts.
+summary vocabulary for connection health and contribution counts. While VMware
+remains admitted rather than supported, shared settings primitives must render
+its source-picker card with the manifest-derived preview badge and keep
+supported-source empty-state copy from listing VMware as available now.
That same shared filter-presentation boundary also owns infrastructure
route-filter continuity. `frontend-modern/src/features/infrastructure/`
must keep a route-owned canonical source option such as `truenas` visible in
diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md
index d187fcf8c..6645b5546 100644
--- a/docs/release-control/v6/internal/subsystems/unified-resources.md
+++ b/docs/release-control/v6/internal/subsystems/unified-resources.md
@@ -1021,7 +1021,11 @@ must stay generated from
`frontend-modern/src/types/resource.ts` must derive `PlatformType` from that
generated supported-plus-admitted projection rather than hand-maintaining a
second platform union that can drift from the governed manifest or re-admit
-presentation-only labels by mistake. Agent host profiles are generated beside
+presentation-only labels by mistake. The supported/admitted split in that
+projection is also contractual: VMware may stay a valid `PlatformType` and
+source-platform alias while admitted, but unified-resource consumers must not
+infer current platform support from that union without checking governance
+state/readiness. Agent host profiles are generated beside
that platform projection for shared identity/presentation use only; a profile
such as Unraid may label a Pulse Agent host, but it must not enter
`PlatformType`, `PLATFORM_TYPE_KEYS`, unified-resource source filters, or
diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureSourcePicker.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureSourcePicker.test.tsx
index eaf68c2c2..d26a1ed1a 100644
--- a/frontend-modern/src/components/Settings/__tests__/InfrastructureSourcePicker.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureSourcePicker.test.tsx
@@ -7,7 +7,7 @@ describe('InfrastructureSourcePicker', () => {
cleanup();
});
- it('labels admitted VMware as Early support and keeps Pulse Agent on the host profile path', () => {
+ it('labels admitted VMware as Preview and keeps Pulse Agent on the host profile path', () => {
render(() => );
expect(screen.getByPlaceholderText('Search platforms, hosts, services...')).toBeInTheDocument();
@@ -17,10 +17,10 @@ describe('InfrastructureSourcePicker', () => {
expect(screen.getByText('Choose how Pulse should connect')).toBeInTheDocument();
expect(screen.getByText('Or pick a specific platform')).toBeInTheDocument();
// VMware lives behind 'Show more platforms' since it is a less common
- // homelab choice; expand the long tail to verify the Early support
- // badge still renders for it.
+ // homelab choice; expand the long tail to verify the Preview badge still
+ // renders for it.
fireEvent.click(screen.getByRole('button', { name: /Show \d+ more platforms?/i }));
- expect(screen.getByText('Early support')).toBeInTheDocument();
+ expect(screen.getByText('Preview')).toBeInTheDocument();
expect(screen.queryByText('Available now')).toBeNull();
expect(screen.getByText('Unraid')).toBeInTheDocument();
expect(screen.getByText('Linux, macOS, Windows host')).toBeInTheDocument();
diff --git a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx
index 837e7c21e..5ad06fbe0 100644
--- a/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/InfrastructureWorkspace.test.tsx
@@ -489,7 +489,10 @@ describe('InfrastructureWorkspace', () => {
expect(
screen.getByText('Choose an infrastructure source to start monitoring your environment.'),
).toBeInTheDocument();
- expect(screen.getByText(/Supported source types include VMware vCenter/i)).toBeInTheDocument();
+ expect(screen.getByText(/Supported source types include TrueNAS SCALE/i)).toBeInTheDocument();
+ expect(
+ screen.getByText(/VMware vCenter is available as a preview platform/i),
+ ).toBeInTheDocument();
expect(screen.getByText(/standalone hosts through Pulse Agent/i)).toBeInTheDocument();
});
diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
index 8910f3f38..3f0e026f8 100644
--- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
+++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
@@ -685,7 +685,7 @@ describe('settings architecture guardrails', () => {
'getInfrastructureSupportSummaryBadges',
);
expect(infrastructureOnboardingPresentationSource).toContain(
- 'Supported source types include VMware vCenter',
+ 'VMware vCenter is available as a preview platform',
);
expect(nodeCredentialSlotSource).toContain('useNodeModalState(modalProps)');
diff --git a/frontend-modern/src/features/infrastructureNavigation/__tests__/infrastructureNavigationModel.test.ts b/frontend-modern/src/features/infrastructureNavigation/__tests__/infrastructureNavigationModel.test.ts
index bc6ca03bb..202bbde2d 100644
--- a/frontend-modern/src/features/infrastructureNavigation/__tests__/infrastructureNavigationModel.test.ts
+++ b/frontend-modern/src/features/infrastructureNavigation/__tests__/infrastructureNavigationModel.test.ts
@@ -21,7 +21,7 @@ const resource = (overrides: Partial): Resource =>
}) as Resource;
describe('infrastructureNavigationModel', () => {
- it('shows primary infrastructure destinations only when supported resource evidence is present', () => {
+ it('shows primary infrastructure destinations only when supported or admitted resource evidence is present', () => {
expect(buildPrimaryInfrastructureNavigationVisibility([])).toEqual({
proxmox: false,
docker: false,
diff --git a/frontend-modern/src/features/infrastructureNavigation/infrastructureNavigationModel.ts b/frontend-modern/src/features/infrastructureNavigation/infrastructureNavigationModel.ts
index c912a9fe7..432dfef5a 100644
--- a/frontend-modern/src/features/infrastructureNavigation/infrastructureNavigationModel.ts
+++ b/frontend-modern/src/features/infrastructureNavigation/infrastructureNavigationModel.ts
@@ -1,6 +1,7 @@
import type { Accessor } from 'solid-js';
import type { Resource, ResourceType } from '@/types/resource';
import {
+ ADMITTED_PLATFORM_IDS,
SUPPORTED_PLATFORM_IDS,
getSourcePlatformManifestEntry,
type SourcePlatformManifestEntry,
@@ -35,7 +36,10 @@ export const PRIMARY_INFRASTRUCTURE_NAV_SCOPE_IDS: Record<
vmware: ['vmware-vsphere'],
};
-const SUPPORTED_PLATFORM_ID_SET = new Set(SUPPORTED_PLATFORM_IDS);
+const NAVIGABLE_PLATFORM_ID_SET = new Set([
+ ...SUPPORTED_PLATFORM_IDS,
+ ...ADMITTED_PLATFORM_IDS,
+]);
const KUBERNETES_RESOURCE_TYPES = new Set([
'k8s-cluster',
@@ -127,7 +131,7 @@ export function collectResourcePlatformEvidence(resource: Resource): string[] {
return [...ids];
}
-export function buildSupportedResourceInfrastructureScopeSet(
+export function buildNavigableResourceInfrastructureScopeSet(
resources: readonly Resource[],
): Set {
const present = new Set();
@@ -136,7 +140,7 @@ export function buildSupportedResourceInfrastructureScopeSet(
const manifestEntry = getSourcePlatformManifestEntry(
platformId,
) as SourcePlatformManifestEntry | null;
- if (!manifestEntry || !SUPPORTED_PLATFORM_ID_SET.has(manifestEntry.id)) continue;
+ if (!manifestEntry || !NAVIGABLE_PLATFORM_ID_SET.has(manifestEntry.id)) continue;
present.add(manifestEntry.id);
}
}
@@ -146,22 +150,22 @@ export function buildSupportedResourceInfrastructureScopeSet(
export function buildPrimaryInfrastructureNavigationVisibility(
resources: readonly Resource[],
): InfrastructureNavigationVisibility {
- const presentSupportedScopes = buildSupportedResourceInfrastructureScopeSet(resources);
+ const presentNavigableScopes = buildNavigableResourceInfrastructureScopeSet(resources);
return {
proxmox: PRIMARY_INFRASTRUCTURE_NAV_SCOPE_IDS.proxmox.some((id) =>
- presentSupportedScopes.has(id),
+ presentNavigableScopes.has(id),
),
docker: PRIMARY_INFRASTRUCTURE_NAV_SCOPE_IDS.docker.some((id) =>
- presentSupportedScopes.has(id),
+ presentNavigableScopes.has(id),
),
kubernetes: PRIMARY_INFRASTRUCTURE_NAV_SCOPE_IDS.kubernetes.some((id) =>
- presentSupportedScopes.has(id),
+ presentNavigableScopes.has(id),
),
truenas: PRIMARY_INFRASTRUCTURE_NAV_SCOPE_IDS.truenas.some((id) =>
- presentSupportedScopes.has(id),
+ presentNavigableScopes.has(id),
),
vmware: PRIMARY_INFRASTRUCTURE_NAV_SCOPE_IDS.vmware.some((id) =>
- presentSupportedScopes.has(id),
+ presentNavigableScopes.has(id),
),
};
}
diff --git a/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts b/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts
index 28812d576..d9a1ebe0e 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 supported vCenter path', () => {
+ it('keeps VMware on the admitted vCenter path until live proof lands', () => {
const vmware = getInfrastructureOnboardingProductPresentation('vmware');
expect(vmware.label).toBe('VMware vCenter');
- expect(vmware.governanceState).toBe('supported');
- expect(vmware.readinessStage).toBe('supported');
+ expect(vmware.governanceState).toBe('admitted');
+ expect(vmware.readinessStage).toBe('first-lab-ready');
expect(vmware.primaryMode).toBe('api-backed');
expect(vmware.canonicalProjections).toEqual(['agent', 'vm', 'storage']);
expect(vmware.supportFloor).toMatchObject({
@@ -87,19 +87,22 @@ describe('infrastructureOnboardingPresentation', () => {
expect(INFRASTRUCTURE_ONBOARDING_PATHS.agent.title).toBe('Install Pulse Agent');
});
- it('lists all supported API products alongside vSphere now that VMware is promoted', () => {
+ it('separates supported API products from admitted preview products', () => {
expect(
getInfrastructureApiProductsByGovernanceState('supported').map((product) => product.label),
).toEqual([
- 'VMware vCenter',
'TrueNAS SCALE',
'Proxmox VE',
'Proxmox Backup Server',
'Proxmox Mail Gateway',
'Network endpoint',
]);
+ expect(
+ getInfrastructureApiProductsByGovernanceState('admitted').map((product) => product.label),
+ ).toEqual(['VMware vCenter']);
expect(getInfrastructureGovernanceBadgeLabel('supported', 'supported')).toBeNull();
+ expect(getInfrastructureGovernanceBadgeLabel('admitted', 'first-lab-ready')).toBe('Preview');
});
it('derives picker items, auto-detect copy, and landing summaries from the shared helper', () => {
@@ -201,7 +204,6 @@ describe('infrastructureOnboardingPresentation', () => {
expect(getInfrastructureSupportSummaryBadges()).toMatchObject({
supportedToday: [
- 'VMware vCenter',
'TrueNAS SCALE',
'Proxmox VE',
'Proxmox Backup Server',
@@ -211,7 +213,7 @@ describe('infrastructureOnboardingPresentation', () => {
'Docker',
'Kubernetes',
],
- currentAdmissionPath: [],
+ currentAdmissionPath: ['VMware vCenter'],
installPath: expect.arrayContaining([
'Linux',
'FreeBSD',
@@ -226,7 +228,10 @@ describe('infrastructureOnboardingPresentation', () => {
'Choose an infrastructure source to start monitoring your environment.',
);
expect(getInfrastructureEmptyStateDetail()).toContain(
- 'Supported source types include VMware vCenter',
+ 'Supported source types include TrueNAS SCALE',
+ );
+ expect(getInfrastructureEmptyStateDetail()).toContain(
+ 'VMware vCenter is available as a preview platform',
);
expect(getInfrastructureEmptyStateDetail()).toContain('standalone hosts through Pulse Agent');
expect(getInfrastructureEmptyStateDetail()).toContain('Docker and Kubernetes are discovered');
diff --git a/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts b/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts
index bed5a17c9..cfba3f03a 100644
--- a/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts
+++ b/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts
@@ -11,6 +11,7 @@ import {
resolveSourceTypeFromSources,
} from '@/utils/sourcePlatforms';
import {
+ ADMITTED_PLATFORM_IDS,
AGENT_HOST_PROFILE_IDS,
PLATFORM_TYPE_KEYS,
PRESENTATION_ONLY_PLATFORM_IDS,
@@ -160,8 +161,9 @@ describe('sourcePlatforms', () => {
});
describe('governed platform support projection', () => {
- it('keeps VMware on the supported vCenter-backed floor', () => {
- expect(getSourcePlatformReadinessStage('vmware')).toBe('supported');
+ it('keeps VMware on the admitted vCenter-backed floor until live proof lands', () => {
+ expect(ADMITTED_PLATFORM_IDS).toEqual(['vmware-vsphere']);
+ expect(getSourcePlatformReadinessStage('vmware')).toBe('first-lab-ready');
expect(getSourcePlatformCanonicalProjections('vmware')).toEqual(['agent', 'vm', 'storage']);
expect(getSourcePlatformSupportFloor('vmware')).toMatchObject({
setup: 'supported',
@@ -197,6 +199,7 @@ describe('sourcePlatforms', () => {
expect(SUPPORTED_RUNTIME_LENS_IDS).toEqual(['docker']);
expect(SUPPORTED_OWNING_PLATFORM_IDS).toContain('proxmox-pve');
expect(SUPPORTED_OWNING_PLATFORM_IDS).not.toContain('docker');
+ expect(SUPPORTED_OWNING_PLATFORM_IDS).not.toContain('vmware-vsphere');
});
});
diff --git a/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts b/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts
index 485e77f80..866459583 100644
--- a/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts
+++ b/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts
@@ -129,11 +129,12 @@ export const getInfrastructureAgentHostProfileSupportText = (): string =>
export const getInfrastructureGovernanceBadgeLabel = (
governanceState: PlatformGovernanceState,
- _readinessStage: PlatformReadinessStage,
+ readinessStage: PlatformReadinessStage,
): string | null => {
if (governanceState === 'supported') return null;
if (governanceState === 'presentation-only') return 'Presentation only';
- return null;
+ if (readinessStage === 'first-lab-ready') return 'Preview';
+ return 'Preview';
};
const SOURCE_STRATEGY_PRESENTATION: Record<
@@ -533,10 +534,9 @@ export const getInfrastructureSupportSummaryBadges = (): {
...getInfrastructureApiProductsByGovernanceState('supported').map((product) => product.label),
...INFRASTRUCTURE_AGENT_DISCOVERY_LABELS,
],
- // No platforms currently sit in the admitted staging area; the type is
- // narrowed to the live governance states, so the admission-path summary
- // is empty until a future platform is admitted-but-not-yet-supported.
- currentAdmissionPath: [],
+ currentAdmissionPath: getInfrastructureApiProductsByGovernanceState('admitted').map(
+ (product) => product.label,
+ ),
installPath: [...INFRASTRUCTURE_AGENT_HOST_LABELS, ...INFRASTRUCTURE_AGENT_DISCOVERY_LABELS],
});
@@ -544,7 +544,7 @@ export const getInfrastructureEmptyStateSummary = (): string =>
'Choose an infrastructure source to start monitoring your environment.';
export const getInfrastructureEmptyStateDetail = (): string =>
- 'Supported source types include VMware vCenter, TrueNAS SCALE, Proxmox VE, Proxmox Backup Server, Proxmox Mail Gateway, network endpoints, and standalone hosts through Pulse Agent. Docker and Kubernetes are discovered from supported agent hosts.';
+ 'Supported source types include TrueNAS SCALE, Proxmox VE, Proxmox Backup Server, Proxmox Mail Gateway, network endpoints, and standalone hosts through Pulse Agent. VMware vCenter is available as a preview platform pending live support proof. Docker and Kubernetes are discovered from supported agent hosts.';
export const getInfrastructureCoverageCompleteActionPresentation =
(): InfrastructureCoverageCompleteActionPresentation => ({
diff --git a/frontend-modern/src/utils/platformSupportManifest.generated.ts b/frontend-modern/src/utils/platformSupportManifest.generated.ts
index 5b0cc91d7..1fae56fe0 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: 11c137eb7f586114e9f835bc5429f6cae81906e6ce8eb13413df1e51752f31a0
+// Source SHA256: 97b7ac58c2c981867dc4052728587d323fc541221135e6bada1191d9d723206b
export const PLATFORM_SUPPORT_MANIFEST_SOURCE = {
path: 'docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json',
- sha256: '11c137eb7f586114e9f835bc5429f6cae81906e6ce8eb13413df1e51752f31a0',
+ sha256: '97b7ac58c2c981867dc4052728587d323fc541221135e6bada1191d9d723206b',
} as const;
export const PLATFORM_SUPPORT_MANIFEST = {
schemaVersion: 2,
@@ -227,8 +227,8 @@ export const PLATFORM_SUPPORT_MANIFEST = {
id: 'vmware-vsphere',
family: 'VMware',
surfaceKind: 'platform',
- governanceState: 'supported',
- readinessStage: 'supported',
+ governanceState: 'admitted',
+ readinessStage: 'first-lab-ready',
primaryMode: 'api-backed',
onboardingPaths: ['platform-connections'],
canonicalProjections: ['agent', 'vm', 'storage'],
@@ -412,7 +412,6 @@ export const SUPPORTED_PLATFORM_IDS = [
'proxmox-pbs',
'proxmox-pmg',
'truenas',
- 'vmware-vsphere',
] as const;
export const SUPPORTED_OWNING_PLATFORM_IDS = [
'agent',
@@ -421,10 +420,9 @@ export const SUPPORTED_OWNING_PLATFORM_IDS = [
'proxmox-pbs',
'proxmox-pmg',
'truenas',
- 'vmware-vsphere',
] as const;
export const SUPPORTED_RUNTIME_LENS_IDS = ['docker'] as const;
-export const ADMITTED_PLATFORM_IDS = [] as const;
+export const ADMITTED_PLATFORM_IDS = ['vmware-vsphere'] as const;
export const PRESENTATION_ONLY_PLATFORM_IDS = [
'unraid',
'synology-dsm',
@@ -601,7 +599,7 @@ export const SOURCE_PLATFORM_READINESS_STAGE = {
'proxmox-pbs': 'supported',
'proxmox-pmg': 'supported',
truenas: 'supported',
- 'vmware-vsphere': 'supported',
+ 'vmware-vsphere': 'first-lab-ready',
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 48294caa2..8d08e33c6 100644
--- a/internal/mock/platform_support_contract_test.go
+++ b/internal/mock/platform_support_contract_test.go
@@ -172,8 +172,12 @@ func TestPlatformSupportManifestMatchesSupportModel(t *testing.T) {
); diff != "" {
t.Fatalf("agent host profile storage-family drifted from the canonical support model:\n%s", diff)
}
- if diff := diffPlatformSets(supportedSurfaces, manifest.DefaultInfrastructureSourceOrder); diff != "" {
- t.Fatalf("default infrastructure source ordering drifted from the canonical supported platform set:\n%s", diff)
+ defaultInfrastructureSources := uniqueSortedPlatforms(append(
+ append([]string(nil), supportedSurfaces...),
+ admitted...,
+ ))
+ if diff := diffPlatformSets(defaultInfrastructureSources, manifest.DefaultInfrastructureSourceOrder); diff != "" {
+ t.Fatalf("default infrastructure source ordering drifted from the canonical supported/admitted platform set:\n%s", diff)
}
}
@@ -182,14 +186,13 @@ func TestMockCoverageMatchesCurrentSupportedPlatformSet(t *testing.T) {
supported := manifestPlatformsByState(t, manifest, "supported")
checkers := map[string]func(*testing.T, FixtureGraph){
- "agent": assertAgentMockCoverage,
- "docker": assertDockerMockCoverage,
- "kubernetes": assertKubernetesMockCoverage,
- "proxmox-pbs": assertProxmoxPBSMockCoverage,
- "proxmox-pmg": assertProxmoxPMGMockCoverage,
- "proxmox-pve": assertProxmoxPVEMockCoverage,
- "truenas": assertTrueNASMockCoverage,
- "vmware-vsphere": assertVMwareMockCoverage,
+ "agent": assertAgentMockCoverage,
+ "docker": assertDockerMockCoverage,
+ "kubernetes": assertKubernetesMockCoverage,
+ "proxmox-pbs": assertProxmoxPBSMockCoverage,
+ "proxmox-pmg": assertProxmoxPMGMockCoverage,
+ "proxmox-pve": assertProxmoxPVEMockCoverage,
+ "truenas": assertTrueNASMockCoverage,
}
if diff := diffPlatformSets(supported, sortedPlatformKeys(checkers)); diff != "" {
@@ -203,24 +206,24 @@ func TestMockCoverageMatchesCurrentSupportedPlatformSet(t *testing.T) {
}
}
-func TestVMwareFixturesRemainSupportedAtPhase1Floor(t *testing.T) {
+func TestVMwareFixturesRemainAdmittedAtPhase1Floor(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 appear in the current supported platform set after promotion from first-lab-ready")
+ if containsPlatform(supported, "vmware-vsphere") {
+ t.Fatal("vmware-vsphere must not appear in the current supported platform set before live vCenter proof")
}
- if containsPlatform(admitted, "vmware-vsphere") {
- t.Fatal("vmware-vsphere must not remain in the admitted set after promotion to supported")
+ if !containsPlatform(admitted, "vmware-vsphere") {
+ t.Fatal("vmware-vsphere must remain in the admitted set until live vCenter proof promotes it")
}
if containsPlatform(presentationOnly, "vmware-vsphere") {
- t.Fatal("vmware-vsphere must not regress into presentation-only vocabulary once supported")
+ t.Fatal("vmware-vsphere must not regress into presentation-only vocabulary while admitted")
}
- if !strings.Contains(model, "| `vmware-vsphere` |") {
- t.Fatal("expected platform support model to keep the vmware-vsphere support row")
+ if !strings.Contains(model, "| `vmware-vsphere` | VMware | `vCenter` only in phase 1 |") {
+ t.Fatal("expected platform support model to keep the vmware-vsphere admission row")
}
vmwareOnboardingPaths, ok := manifestPlatformOnboardingPaths(manifest)["vmware-vsphere"]
if !ok {
@@ -230,8 +233,11 @@ func TestVMwareFixturesRemainSupportedAtPhase1Floor(t *testing.T) {
t.Fatalf("vmware-vsphere onboarding path drifted from the support model:\n%s", diff)
}
vmwareManifest := requireManifestPlatform(t, manifest, "vmware-vsphere")
- if vmwareManifest.ReadinessStage != "supported" {
- t.Fatalf("vmware readiness stage = %q, want supported", vmwareManifest.ReadinessStage)
+ if vmwareManifest.GovernanceState != "admitted" {
+ t.Fatalf("vmware governance state = %q, want admitted", vmwareManifest.GovernanceState)
+ }
+ if vmwareManifest.ReadinessStage != "first-lab-ready" {
+ t.Fatalf("vmware readiness stage = %q, want first-lab-ready", vmwareManifest.ReadinessStage)
}
if vmwareManifest.PrimaryMode != "api-backed" {
t.Fatalf("vmware primary mode = %q, want api-backed", vmwareManifest.PrimaryMode)
@@ -247,15 +253,7 @@ func TestVMwareFixturesRemainSupportedAtPhase1Floor(t *testing.T) {
}
graph := buildFixtureGraph(DefaultConfig, time.Date(2026, time.April, 10, 12, 0, 0, 0, time.UTC))
- if len(graph.PlatformFixtures.VMware.Hosts) == 0 {
- t.Fatal("expected VMware mock fixtures to include hosts")
- }
- if len(graph.PlatformFixtures.VMware.VMs) == 0 {
- t.Fatal("expected VMware mock fixtures to include VMs")
- }
- if len(graph.PlatformFixtures.VMware.Datastores) == 0 {
- t.Fatal("expected VMware mock fixtures to include datastores")
- }
+ assertVMwareMockCoverage(t, graph)
fixture := DefaultVMwareConnectionFixture()
if fixture.CollectedAt.IsZero() {
diff --git a/internal/platformsupport/manifest_generated.go b/internal/platformsupport/manifest_generated.go
index 792f3287d..4cb9e30fc 100644
--- a/internal/platformsupport/manifest_generated.go
+++ b/internal/platformsupport/manifest_generated.go
@@ -1,7 +1,7 @@
// Code generated by scripts/release_control/generate_platform_support_backend_module.py.
// DO NOT EDIT.
// Source: docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json
-// Source SHA256: 921181082d41829ac4884d00175791157ac500a27888ca2b81f09a9e97989bda
+// Source SHA256: 97b7ac58c2c981867dc4052728587d323fc541221135e6bada1191d9d723206b
package platformsupport
@@ -13,7 +13,7 @@ const ManifestSourcePath = "docs/release-control/v6/internal/PLATFORM_SUPPORT_MA
// ManifestSourceSHA256 is the sha256 of the manifest bytes used to produce
// this projection.
-const ManifestSourceSHA256 = "921181082d41829ac4884d00175791157ac500a27888ca2b81f09a9e97989bda"
+const ManifestSourceSHA256 = "97b7ac58c2c981867dc4052728587d323fc541221135e6bada1191d9d723206b"
// ManifestSchemaVersion is the schema_version field of the manifest.
const ManifestSchemaVersion = 2
diff --git a/scripts/release_control/generate_platform_support_frontend_module.py b/scripts/release_control/generate_platform_support_frontend_module.py
index 099014f39..ebd87e02f 100755
--- a/scripts/release_control/generate_platform_support_frontend_module.py
+++ b/scripts/release_control/generate_platform_support_frontend_module.py
@@ -377,6 +377,9 @@ def normalize_manifest(raw_manifest: dict[str, Any]) -> dict[str, Any]:
supported_ids = [
platform["id"] for platform in platforms if platform["governanceState"] == "supported"
]
+ admitted_ids = [
+ platform["id"] for platform in platforms if platform["governanceState"] == "admitted"
+ ]
supported_owning_platform_ids = [
platform["id"]
for platform in platforms
@@ -387,11 +390,11 @@ def normalize_manifest(raw_manifest: dict[str, Any]) -> dict[str, Any]:
for platform in platforms
if platform["governanceState"] == "supported" and platform["surfaceKind"] == "runtime-lens"
]
- supported_id_set = set(supported_ids)
+ infrastructure_source_id_set = set([*supported_ids, *admitted_ids])
for platform_id in default_order:
- if platform_id not in supported_id_set:
+ if platform_id not in infrastructure_source_id_set:
raise ValueError(
- "default_infrastructure_source_order may only contain supported platforms; "
+ "default_infrastructure_source_order may only contain supported or admitted platforms; "
f"got {platform_id}"
)
@@ -458,9 +461,6 @@ def normalize_manifest(raw_manifest: dict[str, Any]) -> dict[str, Any]:
storage_family_by_id = {
platform["id"]: platform["storageFamily"] for platform in platforms
}
- admitted_ids = [
- platform["id"] for platform in platforms if platform["governanceState"] == "admitted"
- ]
presentation_only_ids = [
platform["id"]
for platform in platforms