diff --git a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json index 4bb2b2490..7be0f8e18 100644 --- a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json +++ b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json @@ -14,154 +14,434 @@ "id": "agent", "family": "Pulse-managed host", "governance_state": "supported", - "onboarding_paths": ["install-workspace"], + "readiness_stage": "supported", + "primary_mode": "agent-backed", + "onboarding_paths": [ + "install-workspace" + ], + "canonical_projections": [ + "agent", + "storage", + "physical-disk" + ], + "support_floor": { + "setup": "supported", + "visibility": "supported", + "workloads": "n/a", + "storage": "supported", + "recovery": "n/a", + "alerts": "supported", + "assistant_read": "supported", + "assistant_control": "supported" + }, "ui_label": "Agent", "ui_tone": "bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-400", "aliases": [], - "display_tokens": ["Agent"], + "display_tokens": [ + "Agent" + ], "storage_family": "onprem" }, { "id": "docker", "family": "Container runtime", "governance_state": "supported", - "onboarding_paths": ["install-workspace"], + "readiness_stage": "supported", + "primary_mode": "agent-backed", + "onboarding_paths": [ + "install-workspace" + ], + "canonical_projections": [ + "agent", + "app-container", + "docker-service" + ], + "support_floor": { + "setup": "supported", + "visibility": "supported", + "workloads": "supported", + "storage": "n/a", + "recovery": "n/a", + "alerts": "supported", + "assistant_read": "supported", + "assistant_control": "supported" + }, "ui_label": "Containers", "ui_tone": "bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-400", "aliases": [], - "display_tokens": ["Containers", "Docker"], + "display_tokens": [ + "Containers", + "Docker" + ], "storage_family": "container" }, { "id": "kubernetes", "family": "Cluster runtime", "governance_state": "supported", - "onboarding_paths": ["install-workspace"], + "readiness_stage": "supported", + "primary_mode": "agent-backed", + "onboarding_paths": [ + "install-workspace" + ], + "canonical_projections": [ + "k8s-cluster", + "k8s-node", + "pod", + "k8s-deployment" + ], + "support_floor": { + "setup": "supported", + "visibility": "supported", + "workloads": "supported", + "storage": "n/a", + "recovery": "supported", + "alerts": "supported", + "assistant_read": "supported", + "assistant_control": "supported" + }, "ui_label": "K8s", "ui_tone": "bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400", - "aliases": ["k8s"], - "display_tokens": ["K8s", "Kubernetes"], + "aliases": [ + "k8s" + ], + "display_tokens": [ + "K8s", + "Kubernetes" + ], "storage_family": "container" }, { "id": "proxmox-pve", "family": "Proxmox", "governance_state": "supported", - "onboarding_paths": ["platform-connections"], + "readiness_stage": "supported", + "primary_mode": "api-backed", + "onboarding_paths": [ + "platform-connections" + ], + "canonical_projections": [ + "agent", + "vm", + "system-container", + "storage", + "ceph", + "physical-disk" + ], + "support_floor": { + "setup": "supported", + "visibility": "supported", + "workloads": "supported", + "storage": "supported", + "recovery": "supported", + "alerts": "supported", + "assistant_read": "supported", + "assistant_control": "augmentation-only" + }, "ui_label": "PVE", "ui_tone": "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-400", - "aliases": ["pve", "proxmox"], - "display_tokens": ["PVE", "Proxmox VE"], + "aliases": [ + "pve", + "proxmox" + ], + "display_tokens": [ + "PVE", + "Proxmox VE" + ], "storage_family": "virtualization" }, { "id": "proxmox-pbs", "family": "Proxmox", "governance_state": "supported", - "onboarding_paths": ["platform-connections"], + "readiness_stage": "supported", + "primary_mode": "api-backed", + "onboarding_paths": [ + "platform-connections" + ], + "canonical_projections": [ + "pbs", + "storage" + ], + "support_floor": { + "setup": "supported", + "visibility": "supported", + "workloads": "n/a", + "storage": "supported", + "recovery": "supported", + "alerts": "supported", + "assistant_read": "supported", + "assistant_control": "read-only" + }, "ui_label": "PBS", "ui_tone": "bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-400", - "aliases": ["pbs"], - "display_tokens": ["PBS", "Proxmox Backup Server"], + "aliases": [ + "pbs" + ], + "display_tokens": [ + "PBS", + "Proxmox Backup Server" + ], "storage_family": "virtualization" }, { "id": "proxmox-pmg", "family": "Proxmox", "governance_state": "supported", - "onboarding_paths": ["platform-connections"], + "readiness_stage": "supported", + "primary_mode": "api-backed", + "onboarding_paths": [ + "platform-connections" + ], + "canonical_projections": [ + "pmg" + ], + "support_floor": { + "setup": "supported", + "visibility": "supported", + "workloads": "n/a", + "storage": "n/a", + "recovery": "n/a", + "alerts": "supported", + "assistant_read": "supported", + "assistant_control": "read-only" + }, "ui_label": "PMG", "ui_tone": "bg-rose-100 text-rose-700 dark:bg-rose-900 dark:text-rose-400", - "aliases": ["pmg"], - "display_tokens": ["PMG", "Proxmox Mail Gateway"], + "aliases": [ + "pmg" + ], + "display_tokens": [ + "PMG", + "Proxmox Mail Gateway" + ], "storage_family": "virtualization" }, { "id": "truenas", "family": "TrueNAS", "governance_state": "supported", - "onboarding_paths": ["platform-connections"], + "readiness_stage": "supported", + "primary_mode": "api-backed", + "onboarding_paths": [ + "platform-connections" + ], + "canonical_projections": [ + "agent", + "app-container", + "storage", + "physical-disk" + ], + "support_floor": { + "setup": "supported", + "visibility": "supported", + "workloads": "supported", + "storage": "supported", + "recovery": "supported", + "alerts": "supported", + "assistant_read": "supported", + "assistant_control": "supported" + }, "ui_label": "TrueNAS", "ui_tone": "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-400", "aliases": [], - "display_tokens": ["TrueNAS"], + "display_tokens": [ + "TrueNAS" + ], "storage_family": "onprem" }, { "id": "vmware-vsphere", "family": "VMware", "governance_state": "admitted", - "onboarding_paths": ["platform-connections"], + "readiness_stage": "first-lab-ready", + "primary_mode": "api-backed", + "onboarding_paths": [ + "platform-connections" + ], + "canonical_projections": [ + "agent", + "vm", + "storage" + ], + "support_floor": { + "setup": "supported", + "visibility": "supported", + "workloads": "supported", + "storage": "supported", + "recovery": "n/a", + "alerts": "supported", + "assistant_read": "supported", + "assistant_control": "read-only" + }, "ui_label": "vSphere", "ui_tone": "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300", - "aliases": ["vmware"], - "display_tokens": ["vSphere", "VMware vSphere"], + "aliases": [ + "vmware" + ], + "display_tokens": [ + "vSphere", + "VMware vSphere" + ], "storage_family": "virtualization" }, { "id": "unraid", "family": "Unraid", "governance_state": "presentation-only", + "readiness_stage": "presentation-only", + "primary_mode": "presentation-only", "onboarding_paths": [], + "canonical_projections": [], + "support_floor": { + "setup": "n/a", + "visibility": "n/a", + "workloads": "n/a", + "storage": "n/a", + "recovery": "n/a", + "alerts": "n/a", + "assistant_read": "n/a", + "assistant_control": "n/a" + }, "ui_label": "Unraid", "ui_tone": "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300", "aliases": [], - "display_tokens": ["Unraid"], + "display_tokens": [ + "Unraid" + ], "storage_family": "onprem" }, { "id": "synology-dsm", "family": "Synology", "governance_state": "presentation-only", + "readiness_stage": "presentation-only", + "primary_mode": "presentation-only", "onboarding_paths": [], + "canonical_projections": [], + "support_floor": { + "setup": "n/a", + "visibility": "n/a", + "workloads": "n/a", + "storage": "n/a", + "recovery": "n/a", + "alerts": "n/a", + "assistant_read": "n/a", + "assistant_control": "n/a" + }, "ui_label": "Synology", "ui_tone": "bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300", "aliases": [], - "display_tokens": ["Synology", "DSM"], + "display_tokens": [ + "Synology", + "DSM" + ], "storage_family": "onprem" }, { "id": "microsoft-hyperv", "family": "Hyper-V", "governance_state": "presentation-only", + "readiness_stage": "presentation-only", + "primary_mode": "presentation-only", "onboarding_paths": [], + "canonical_projections": [], + "support_floor": { + "setup": "n/a", + "visibility": "n/a", + "workloads": "n/a", + "storage": "n/a", + "recovery": "n/a", + "alerts": "n/a", + "assistant_read": "n/a", + "assistant_control": "n/a" + }, "ui_label": "Hyper-V", "ui_tone": "bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-300", - "aliases": ["hyper-v"], - "display_tokens": ["Hyper-V"], + "aliases": [ + "hyper-v" + ], + "display_tokens": [ + "Hyper-V" + ], "storage_family": "virtualization" }, { "id": "aws", "family": "AWS", "governance_state": "presentation-only", + "readiness_stage": "presentation-only", + "primary_mode": "presentation-only", "onboarding_paths": [], + "canonical_projections": [], + "support_floor": { + "setup": "n/a", + "visibility": "n/a", + "workloads": "n/a", + "storage": "n/a", + "recovery": "n/a", + "alerts": "n/a", + "assistant_read": "n/a", + "assistant_control": "n/a" + }, "ui_label": "AWS", "ui_tone": "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300", "aliases": [], - "display_tokens": ["AWS"], + "display_tokens": [ + "AWS" + ], "storage_family": "cloud" }, { "id": "azure", "family": "Azure", "governance_state": "presentation-only", + "readiness_stage": "presentation-only", + "primary_mode": "presentation-only", "onboarding_paths": [], + "canonical_projections": [], + "support_floor": { + "setup": "n/a", + "visibility": "n/a", + "workloads": "n/a", + "storage": "n/a", + "recovery": "n/a", + "alerts": "n/a", + "assistant_read": "n/a", + "assistant_control": "n/a" + }, "ui_label": "Azure", "ui_tone": "bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-300", "aliases": [], - "display_tokens": ["Azure"], + "display_tokens": [ + "Azure" + ], "storage_family": "cloud" }, { "id": "gcp", "family": "GCP", "governance_state": "presentation-only", + "readiness_stage": "presentation-only", + "primary_mode": "presentation-only", "onboarding_paths": [], + "canonical_projections": [], + "support_floor": { + "setup": "n/a", + "visibility": "n/a", + "workloads": "n/a", + "storage": "n/a", + "recovery": "n/a", + "alerts": "n/a", + "assistant_read": "n/a", + "assistant_control": "n/a" + }, "ui_label": "GCP", "ui_tone": "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300", "aliases": [], - "display_tokens": ["GCP"], + "display_tokens": [ + "GCP" + ], "storage_family": "cloud" } ] diff --git a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md index a603a44f2..61e9daff1 100644 --- a/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md +++ b/docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md @@ -1,6 +1,6 @@ # Pulse v6 Platform Support Model -Last updated: 2026-04-21 +Last updated: 2026-04-25 Status: ACTIVE This file is the canonical governed model for platform support in Pulse v6. @@ -221,17 +221,40 @@ Rules: 5. `azure` 6. `gcp` +### Current support rows + +Support floor fields are recorded in this order: `setup`, `visibility`, +`workloads`, `storage`, `recovery`, `alerts`, `assistant_read`, +`assistant_control`. + +| Platform | Governance | Readiness | Primary mode | Onboarding | Canonical projections | Support floor | +| --- | --- | --- | --- | --- | --- | --- | +| `agent` | `supported` | `supported` | `agent-backed` | `install-workspace` | `agent`, `storage`, `physical-disk` | `setup=supported`; `visibility=supported`; `workloads=n/a`; `storage=supported`; `recovery=n/a`; `alerts=supported`; `assistant_read=supported`; `assistant_control=supported` | +| `docker` | `supported` | `supported` | `agent-backed` | `install-workspace` | `agent`, `app-container`, `docker-service` | `setup=supported`; `visibility=supported`; `workloads=supported`; `storage=n/a`; `recovery=n/a`; `alerts=supported`; `assistant_read=supported`; `assistant_control=supported` | +| `kubernetes` | `supported` | `supported` | `agent-backed` | `install-workspace` | `k8s-cluster`, `k8s-node`, `pod`, `k8s-deployment` | `setup=supported`; `visibility=supported`; `workloads=supported`; `storage=n/a`; `recovery=supported`; `alerts=supported`; `assistant_read=supported`; `assistant_control=supported` | +| `proxmox-pve` | `supported` | `supported` | `api-backed` | `platform-connections` | `agent`, `vm`, `system-container`, `storage`, `ceph`, `physical-disk` | `setup=supported`; `visibility=supported`; `workloads=supported`; `storage=supported`; `recovery=supported`; `alerts=supported`; `assistant_read=supported`; `assistant_control=augmentation-only` | +| `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` | + +Presentation-only platform vocabulary uses `governance=presentation-only`, +`readiness=presentation-only`, `primary_mode=presentation-only`, no onboarding +paths, no canonical projections, and `n/a` for every support-floor field. + ### Machine-readable projection `PLATFORM_SUPPORT_MANIFEST.json` is the machine-readable projection of the supported, admitted, and presentation-only platform vocabulary declared here, -plus the canonical platform-family and onboarding-path classification for -supported and admitted platforms. Tests and shared frontend vocabulary may -consume that manifest, and the tracked frontend projection in +plus the canonical platform-family, readiness-stage, primary-mode, +onboarding-path, projection, and support-floor classification for supported +and admitted platforms. Tests and shared frontend vocabulary may consume that +manifest, and the tracked frontend projection in `frontend-modern/src/utils/platformSupportManifest.generated.ts` must be generated from it, but neither projection may introduce platform ids or -governance states or platform families or onboarding paths that are not -declared in this document. +governance states, platform families, readiness stages, primary modes, +onboarding paths, projections, or support-floor claims that are not declared in +this document. ### Runtime variants diff --git a/docs/release-control/v6/internal/status.json b/docs/release-control/v6/internal/status.json index 9f1c7c810..7fe005b1e 100644 --- a/docs/release-control/v6/internal/status.json +++ b/docs/release-control/v6/internal/status.json @@ -3858,6 +3858,187 @@ "kind": "file" } ] + }, + { + "id": "L21", + "name": "Platform admission execution", + "target_score": 6, + "current_score": 6, + "status": "partial", + "completion": { + "state": "bounded-residual", + "summary": "Platform admission execution now has a first-class governed floor: the platform support manifest carries readiness stage, primary mode, canonical projections, onboarding paths, and per-surface support-floor classification into a generated frontend projection, while VMware vSphere remains admitted at first-lab-ready instead of being mislabeled as fully supported. Live vCenter proof, deeper recovery/control coverage, and future admitted-platform runtime depth remain a named post-RC hardening track.", + "tracking": [ + { + "kind": "lane-followup", + "id": "platform-admission-execution-post-rc-hardening" + } + ] + }, + "blockers": [], + "subsystems": [], + "evidence": [ + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/records/vmware-vcenter-phase1-proof-blocked-2026-03-30.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/agent-lifecycle.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/ai-runtime.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/alerts.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/api-contracts.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/frontend-primitives.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/monitoring.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/registry.json", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/storage-recovery.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/unified-resources.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_ALERTS_AND_ASSISTANT_SPEC.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_API_RUNTIME_SPEC.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_ONBOARDING_SPEC.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_PROOF_MATRIX.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_RESOURCE_PROJECTION_SPEC.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/scripts/canonical-platform-audit.mjs", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/types/__tests__/resource.test.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/__tests__/sourcePlatformOptions.test.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/infrastructureOnboardingPresentation.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/platformSupportManifest.generated.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/platformSupportManifest.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/sourcePlatformOptions.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "frontend-modern/src/utils/sourcePlatforms.ts", + "kind": "file" + }, + { + "repo": "pulse", + "path": "internal/api/vmware_handlers.go", + "kind": "file" + }, + { + "repo": "pulse", + "path": "internal/mock/platform_support_contract_test.go", + "kind": "file" + }, + { + "repo": "pulse", + "path": "internal/monitoring/vmware_poller.go", + "kind": "file" + }, + { + "repo": "pulse", + "path": "internal/vmware/provider.go", + "kind": "file" + }, + { + "repo": "pulse", + "path": "scripts/release_control/generate_platform_support_frontend_module.py", + "kind": "file" + } + ] } ], "release_gates": [ @@ -4565,6 +4746,17 @@ ], "subsystem_ids": [] }, + { + "id": "platform-admission-execution-post-rc-hardening", + "summary": "Track broader platform-admission execution beyond the current first-lab-ready support-floor projection, including live vCenter proof before VMware can move to supported, recovery/control expansion, and future admitted-platform proof across setup, canonical projections, alerts, assistant read, and bounded control.", + "owner": "project-owner", + "status": "planned", + "recorded_at": "2026-04-25", + "lane_ids": [ + "L21" + ], + "subsystem_ids": [] + }, { "id": "policy-aware-data-governance-post-rc-hardening", "summary": "Track broader policy-aware data-governance hardening beyond the current resource-policy floor, including enterprise DLP posture, provider-retention disclosure, non-resource prompt-secret detection, and additional external-model boundary proof where future paid or hosted AI surfaces add new data paths.", @@ -4615,110 +4807,6 @@ "kind": "file" } ] - }, - { - "id": "platform-admission-execution", - "summary": "The platform support model now governs admission decisions, and VMware vSphere is already the current admitted strategic next-platform direction, but the lane map still underrepresents the cross-surface work needed to carry an admitted first-class platform from architecture lock to a proved support floor across setup, canonical projections, alerts, assistant read, and bounded control classification.", - "owner": "project-owner", - "status": "planned", - "recorded_at": "2026-03-30", - "lane_ids": [ - "L6", - "L13", - "L15" - ], - "subsystem_ids": [ - "ai-runtime", - "alerts", - "api-contracts", - "monitoring", - "storage-recovery", - "unified-resources" - ], - "proposed_resolution": "new-lane", - "coverage_impact": 12, - "evidence": [ - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/records/vmware-vcenter-phase1-proof-blocked-2026-03-30.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/SOURCE_OF_TRUTH.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/subsystems/agent-lifecycle.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/subsystems/ai-runtime.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/subsystems/alerts.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/subsystems/monitoring.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/subsystems/storage-recovery.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/subsystems/unified-resources.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_ALERTS_AND_ASSISTANT_SPEC.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_API_RUNTIME_SPEC.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_ONBOARDING_SPEC.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_PROOF_MATRIX.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_PROOF_RECORD_TEMPLATE.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/VMWARE_VCENTER_PHASE1_RESOURCE_PROJECTION_SPEC.md", - "kind": "file" - }, - { - "repo": "pulse", - "path": "docs/release-control/v6/internal/VMWARE_VSPHERE_PHASE1_EXECUTION_PLAN.md", - "kind": "file" - } - ] } ], "candidate_lanes": [ @@ -4738,30 +4826,6 @@ "subsystem_ids": [ "agent-lifecycle" ] - }, - { - "id": "platform-admission-execution", - "name": "Platform admission execution", - "summary": "Carry admitted first-class platform directions from architecture lock to proved support floors through one governed lane covering shared onboarding, canonical resource projection, support-floor proof, and bounded assistant/control classification, starting with the active VMware vSphere direction.", - "status": "accepted", - "recorded_at": "2026-03-30", - "target_id": "v6-product-lane-expansion", - "current_lane_ids": [ - "L6", - "L13", - "L15" - ], - "coverage_gap_ids": [ - "platform-admission-execution" - ], - "subsystem_ids": [ - "ai-runtime", - "alerts", - "api-contracts", - "monitoring", - "storage-recovery", - "unified-resources" - ] } ], "work_claims": [], diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 976e2933c..1236c25e5 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -131,6 +131,14 @@ platform-connection lifecycle state. Once a TrueNAS or VMware setup form marks the connection disabled, lifecycle surfaces must treat a canonical zero-delta or removal-only preview as a valid save path instead of holding the dialog in an add-only capacity posture. +The lifecycle-owned onboarding presentation helper must consume the governed +platform support manifest for readiness stage, primary mode, canonical +projections, and support-floor posture. +`frontend-modern/src/utils/infrastructureOnboardingPresentation.ts` may adapt +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. ## Extension Points diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 8ef8042ad..0140d9342 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -148,6 +148,11 @@ work extends shared components instead of creating new local variants. 123. `frontend-modern/scripts/header-audit.mjs` 124. `frontend-modern/src/components/Settings/DataHandlingPanel.tsx` 125. `frontend-modern/src/components/Settings/dataHandlingPanelModel.ts` +126. `frontend-modern/scripts/canonical-platform-audit.mjs` +127. `frontend-modern/src/utils/platformSupportManifest.generated.ts` +128. `frontend-modern/src/utils/platformSupportManifest.ts` +129. `frontend-modern/src/utils/sourcePlatformOptions.ts` +130. `frontend-modern/src/utils/sourcePlatforms.ts` ## Shared Boundaries @@ -157,6 +162,8 @@ work extends shared components instead of creating new local variants. 4. `frontend-modern/src/components/Settings/SecurityAuthPanel.tsx` shared with `security-privacy`: the authentication settings surface is both a security/privacy control surface and a canonical settings-shell presentation boundary. 5. `frontend-modern/src/components/Settings/SecurityOverviewPanel.tsx` shared with `security-privacy`: the security overview settings surface is both a security/privacy control surface and a canonical settings-shell presentation boundary. 6. `frontend-modern/src/stores/aiChat.ts` shared with `ai-runtime`: the assistant drawer and session store is both an AI runtime control surface and a canonical app-shell presentation boundary. +7. `frontend-modern/src/utils/platformSupportManifest.generated.ts` shared with `unified-resources`: the generated platform support projection is both a canonical unified-resource platform union boundary and a shared frontend source/platform vocabulary boundary. +8. `frontend-modern/src/utils/sourcePlatforms.ts` shared with `unified-resources`: the source platform normalizer is both a canonical unified-resource source adapter boundary and a shared frontend source/platform vocabulary boundary. ## Extension Points diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index 4244099aa..236304b75 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -393,6 +393,22 @@ "api-contracts" ] }, + { + "path": "frontend-modern/src/utils/platformSupportManifest.generated.ts", + "rationale": "the generated platform support projection is both a canonical unified-resource platform union boundary and a shared frontend source/platform vocabulary boundary", + "subsystems": [ + "frontend-primitives", + "unified-resources" + ] + }, + { + "path": "frontend-modern/src/utils/sourcePlatforms.ts", + "rationale": "the source platform normalizer is both a canonical unified-resource source adapter boundary and a shared frontend source/platform vocabulary boundary", + "subsystems": [ + "frontend-primitives", + "unified-resources" + ] + }, { "path": "internal/api/access_control_handlers.go", "rationale": "RBAC role and user-assignment handlers are both an organization settings control surface and a canonical API payload contract boundary", @@ -687,6 +703,7 @@ "frontend-modern/src/utils/clusterEndpointPresentation.ts", "frontend-modern/src/utils/configuredNodeCapabilityPresentation.ts", "frontend-modern/src/utils/configuredNodeStatusPresentation.ts", + "frontend-modern/src/utils/infrastructureOnboardingPresentation.ts", "frontend-modern/src/utils/infrastructureSettingsPresentation.ts", "frontend-modern/src/utils/nodeModalPresentation.ts", "frontend-modern/src/utils/proxmoxSettingsPresentation.ts", @@ -858,6 +875,20 @@ "frontend-modern/src/utils/__tests__/agentInstallCommand.test.ts" ] }, + { + "id": "infrastructure-onboarding-presentation-helper", + "label": "infrastructure onboarding presentation helper proof", + "match_prefixes": [], + "match_files": [ + "frontend-modern/src/utils/infrastructureOnboardingPresentation.ts" + ], + "allow_same_subsystem_tests": false, + "test_prefixes": [], + "exact_files": [ + "frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts", + "frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts" + ] + }, { "id": "proxmox-lifecycle-client-surface", "label": "proxmox lifecycle API client proof", @@ -2859,6 +2890,7 @@ "frontend-modern/src/components/shared/" ], "owned_files": [ + "frontend-modern/scripts/canonical-platform-audit.mjs", "frontend-modern/scripts/header-audit.mjs", "frontend-modern/src/components/Settings/AIChatMaintenanceSection.tsx", "frontend-modern/src/components/Settings/AIModelSelectionSection.tsx", @@ -2945,12 +2977,16 @@ "frontend-modern/src/utils/discoveryPresentation.ts", "frontend-modern/src/utils/emptyStatePresentation.ts", "frontend-modern/src/utils/environmentLockPresentation.ts", + "frontend-modern/src/utils/platformSupportManifest.generated.ts", + "frontend-modern/src/utils/platformSupportManifest.ts", "frontend-modern/src/utils/problemResourcePresentation.ts", "frontend-modern/src/utils/reportableResourceTypes.ts", "frontend-modern/src/utils/reportingPresentation.ts", "frontend-modern/src/utils/reportingResourceTypes.ts", "frontend-modern/src/utils/semanticTonePresentation.ts", "frontend-modern/src/utils/settingsShellPresentation.ts", + "frontend-modern/src/utils/sourcePlatformOptions.ts", + "frontend-modern/src/utils/sourcePlatforms.ts", "frontend-modern/src/utils/ssoProviderPresentation.ts", "frontend-modern/src/utils/systemLogsPresentation.ts", "frontend-modern/src/utils/systemSettingsPresentation.ts", @@ -3274,6 +3310,25 @@ "frontend-modern/src/utils/__tests__/updatesPresentation.test.ts" ] }, + { + "id": "platform-support-manifest-frontend-projection", + "label": "platform support manifest frontend projection proof", + "match_prefixes": [], + "match_files": [ + "frontend-modern/scripts/canonical-platform-audit.mjs", + "frontend-modern/src/utils/platformSupportManifest.generated.ts", + "frontend-modern/src/utils/platformSupportManifest.ts", + "frontend-modern/src/utils/sourcePlatformOptions.ts", + "frontend-modern/src/utils/sourcePlatforms.ts" + ], + "allow_same_subsystem_tests": false, + "test_prefixes": [], + "exact_files": [ + "frontend-modern/src/types/__tests__/resource.test.ts", + "frontend-modern/src/utils/__tests__/sourcePlatformOptions.test.ts", + "frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts" + ] + }, { "id": "system-logs-presentation-helper", "label": "system logs presentation helper proof", @@ -4841,6 +4896,7 @@ "frontend-modern/src/types/resource.ts", "frontend-modern/src/utils/agentResources.ts", "frontend-modern/src/utils/canonicalResourceTypes.ts", + "frontend-modern/src/utils/platformSupportManifest.generated.ts", "frontend-modern/src/utils/resourceBadgePresentation.ts", "frontend-modern/src/utils/resourceChangePresentation.ts", "frontend-modern/src/utils/resourceCorrelationPresentation.ts", @@ -4851,6 +4907,7 @@ "frontend-modern/src/utils/resourceTypeCompat.ts", "frontend-modern/src/utils/resourceTypePresentation.ts", "frontend-modern/src/utils/serviceHealthPresentation.ts", + "frontend-modern/src/utils/sourcePlatforms.ts", "frontend-modern/src/utils/sourceTypePresentation.ts", "frontend-modern/src/utils/workloadTypePresentation.ts", "internal/api/resources.go" @@ -4998,6 +5055,21 @@ "frontend-modern/src/utils/__tests__/resourceChangePresentation.test.ts" ] }, + { + "id": "platform-support-resource-source-projection", + "label": "platform support resource source projection proof", + "match_prefixes": [], + "match_files": [ + "frontend-modern/src/utils/platformSupportManifest.generated.ts", + "frontend-modern/src/utils/sourcePlatforms.ts" + ], + "allow_same_subsystem_tests": false, + "test_prefixes": [], + "exact_files": [ + "frontend-modern/src/types/__tests__/resource.test.ts", + "frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts" + ] + }, { "id": "resource-runtime-adapter-helpers", "label": "resource runtime adapter helper proof", diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index d857ce2be..f09f7e42b 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -100,8 +100,9 @@ cross-source deduplication. 78. `frontend-modern/src/hooks/useUnifiedResources.ts` 79. `frontend-modern/src/types/resource.ts` 80. `frontend-modern/src/utils/sourcePlatforms.ts` -81. `internal/unifiedresources/kubernetes_metric_ids.go` -82. `internal/unifiedresources/policy_posture.go` +81. `frontend-modern/src/utils/platformSupportManifest.generated.ts` +82. `internal/unifiedresources/kubernetes_metric_ids.go` +83. `internal/unifiedresources/policy_posture.go` ## Shared Boundaries @@ -119,7 +120,9 @@ cross-source deduplication. 12. `frontend-modern/src/components/Infrastructure/useInfrastructureSummaryState.ts` shared with `performance-and-scalability`: infrastructure summary chart polling, cache hydration, and summary-state orchestration are both a canonical unified-resource consumer surface and a fleet-scale summary chart hot-path boundary. 13. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts` shared with `performance-and-scalability`: unified resource table state, grouping, and windowing are both a canonical unified-resource consumer surface and a fleet-scale performance hot-path boundary. 14. `frontend-modern/src/components/Infrastructure/useUnifiedResourceTableViewportSync.ts` shared with `performance-and-scalability`: unified resource table viewport sync and selected-row reveal are both a canonical unified-resource consumer surface and a fleet-scale performance hot-path boundary. -15. `internal/api/resources.go` shared with `api-contracts`: the unified resource endpoint is both a backend payload contract surface and a unified-resource runtime boundary. +15. `frontend-modern/src/utils/platformSupportManifest.generated.ts` shared with `frontend-primitives`: the generated platform support projection is both a canonical unified-resource platform union boundary and a shared frontend source/platform vocabulary boundary. +16. `frontend-modern/src/utils/sourcePlatforms.ts` shared with `frontend-primitives`: the source platform normalizer is both a canonical unified-resource source adapter boundary and a shared frontend source/platform vocabulary boundary. +17. `internal/api/resources.go` shared with `api-contracts`: the unified resource endpoint is both a backend payload contract surface and a unified-resource runtime boundary. ## Extension Points diff --git a/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts b/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts index ceb4c6fbc..ae3de6c63 100644 --- a/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/infrastructureOnboardingPresentation.test.ts @@ -18,6 +18,19 @@ describe('infrastructureOnboardingPresentation', () => { expect(vmware.label).toBe('VMware vCenter'); 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({ + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'supported', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'read-only', + }); }); it('frames Pulse Agent as the low-overhead per-machine path for full node-local telemetry', () => { diff --git a/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts b/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts index 29fda6920..f959c3ee3 100644 --- a/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts +++ b/frontend-modern/src/utils/__tests__/sourcePlatforms.test.ts @@ -8,6 +8,11 @@ import { resolvePlatformTypeFromSources, resolveSourceTypeFromSources, } from '@/utils/sourcePlatforms'; +import { + getSourcePlatformCanonicalProjections, + getSourcePlatformReadinessStage, + getSourcePlatformSupportFloor, +} from '@/utils/platformSupportManifest'; describe('sourcePlatforms', () => { describe('normalizeSourcePlatformKey', () => { @@ -119,6 +124,23 @@ 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'); + expect(getSourcePlatformCanonicalProjections('vmware')).toEqual(['agent', 'vm', 'storage']); + expect(getSourcePlatformSupportFloor('vmware')).toMatchObject({ + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'supported', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'read-only', + }); + }); + }); + describe('resolvePlatformTypeFromSources', () => { it('prefers canonical platform priority from source arrays', () => { expect(resolvePlatformTypeFromSources(['agent', 'docker'])).toBe('docker'); diff --git a/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts b/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts index 51510face..7d6f6a0f5 100644 --- a/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts +++ b/frontend-modern/src/utils/infrastructureOnboardingPresentation.ts @@ -2,6 +2,9 @@ import type { ConnectionType } from '@/api/connections'; import { getSourcePlatformManifestEntry, type PlatformGovernanceState, + type PlatformPrimaryMode, + type PlatformReadinessStage, + type PlatformSupportFloor, } from '@/utils/platformSupportManifest'; export type InfrastructureOnboardingConnectionType = Extract< @@ -18,6 +21,10 @@ export interface InfrastructureOnboardingProductPresentation { sourceStrategy: InfrastructureSourceStrategy; autoDetect: boolean; governanceState: PlatformGovernanceState; + readinessStage: PlatformReadinessStage; + primaryMode: PlatformPrimaryMode; + canonicalProjections: readonly string[]; + supportFloor: PlatformSupportFloor; defaultSurfaceKeys: readonly string[]; } @@ -167,6 +174,11 @@ const governanceStateForType = ( return getSourcePlatformManifestEntry(sourcePlatformId)?.governanceState ?? 'supported'; }; +const manifestEntryForType = (type: InfrastructureOnboardingConnectionType) => { + const sourcePlatformId = PRODUCT_PRESENTATION[type].sourcePlatformId; + return sourcePlatformId ? getSourcePlatformManifestEntry(sourcePlatformId) : null; +}; + const API_PRODUCT_ORDER: InfrastructureOnboardingConnectionType[] = [ 'vmware', 'truenas', @@ -260,11 +272,29 @@ const SOURCE_PICKER_GROUPS: InfrastructureSourcePickerGroupPresentation[] = [ export const getInfrastructureOnboardingProductPresentation = ( type: InfrastructureOnboardingConnectionType, -): InfrastructureOnboardingProductPresentation => ({ - type, - ...PRODUCT_PRESENTATION[type], - governanceState: governanceStateForType(type), -}); +): InfrastructureOnboardingProductPresentation => { + const manifestEntry = manifestEntryForType(type); + return { + type, + ...PRODUCT_PRESENTATION[type], + governanceState: manifestEntry?.governanceState ?? governanceStateForType(type), + readinessStage: manifestEntry?.readinessStage ?? 'supported', + primaryMode: manifestEntry?.primaryMode ?? 'agent-backed', + canonicalProjections: manifestEntry?.canonicalProjections ?? ['agent'], + supportFloor: + manifestEntry?.supportFloor ?? + ({ + setup: 'supported', + visibility: 'supported', + workloads: 'n/a', + storage: 'supported', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + } satisfies PlatformSupportFloor), + }; +}; export const getInfrastructureSourceStrategyPresentation = ( strategy: InfrastructureSourceStrategy, diff --git a/frontend-modern/src/utils/platformSupportManifest.generated.ts b/frontend-modern/src/utils/platformSupportManifest.generated.ts index fdff0d0fc..0ab5b3158 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: 55c8ab8c350f52aeb9bfa8658ac413b85ef925de54409481f31fbd3a675bf30e +// Source SHA256: 31ea4c3a28507c3d60b40d747b93aadbbf7c5b4b51b546e9e803fb34eb519acd export const PLATFORM_SUPPORT_MANIFEST_SOURCE = { path: 'docs/release-control/v6/internal/PLATFORM_SUPPORT_MANIFEST.json', - sha256: '55c8ab8c350f52aeb9bfa8658ac413b85ef925de54409481f31fbd3a675bf30e', + sha256: '31ea4c3a28507c3d60b40d747b93aadbbf7c5b4b51b546e9e803fb34eb519acd', } as const; export const PLATFORM_SUPPORT_MANIFEST = { schemaVersion: 1, @@ -23,7 +23,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'agent', family: 'Pulse-managed host', governanceState: 'supported', + readinessStage: 'supported', + primaryMode: 'agent-backed', onboardingPaths: ['install-workspace'], + canonicalProjections: ['agent', 'storage', 'physical-disk'], + supportFloor: { + setup: 'supported', + visibility: 'supported', + workloads: 'n/a', + storage: 'supported', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + }, uiLabel: 'Agent', uiTone: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-400', aliases: [], @@ -34,7 +47,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'docker', family: 'Container runtime', governanceState: 'supported', + readinessStage: 'supported', + primaryMode: 'agent-backed', onboardingPaths: ['install-workspace'], + canonicalProjections: ['agent', 'app-container', 'docker-service'], + supportFloor: { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'n/a', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + }, uiLabel: 'Containers', uiTone: 'bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-400', aliases: [], @@ -45,7 +71,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'kubernetes', family: 'Cluster runtime', governanceState: 'supported', + readinessStage: 'supported', + primaryMode: 'agent-backed', onboardingPaths: ['install-workspace'], + canonicalProjections: ['k8s-cluster', 'k8s-node', 'pod', 'k8s-deployment'], + supportFloor: { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'n/a', + recovery: 'supported', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + }, uiLabel: 'K8s', uiTone: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-400', aliases: ['k8s'], @@ -56,7 +95,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'proxmox-pve', family: 'Proxmox', governanceState: 'supported', + readinessStage: 'supported', + primaryMode: 'api-backed', onboardingPaths: ['platform-connections'], + canonicalProjections: ['agent', 'vm', 'system-container', 'storage', 'ceph', 'physical-disk'], + supportFloor: { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'supported', + recovery: 'supported', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'augmentation-only', + }, uiLabel: 'PVE', uiTone: 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-400', aliases: ['pve', 'proxmox'], @@ -67,7 +119,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'proxmox-pbs', family: 'Proxmox', governanceState: 'supported', + readinessStage: 'supported', + primaryMode: 'api-backed', onboardingPaths: ['platform-connections'], + canonicalProjections: ['pbs', 'storage'], + supportFloor: { + setup: 'supported', + visibility: 'supported', + workloads: 'n/a', + storage: 'supported', + recovery: 'supported', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'read-only', + }, uiLabel: 'PBS', uiTone: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-400', aliases: ['pbs'], @@ -78,7 +143,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'proxmox-pmg', family: 'Proxmox', governanceState: 'supported', + readinessStage: 'supported', + primaryMode: 'api-backed', onboardingPaths: ['platform-connections'], + canonicalProjections: ['pmg'], + supportFloor: { + setup: 'supported', + visibility: 'supported', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'read-only', + }, uiLabel: 'PMG', uiTone: 'bg-rose-100 text-rose-700 dark:bg-rose-900 dark:text-rose-400', aliases: ['pmg'], @@ -89,7 +167,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'truenas', family: 'TrueNAS', governanceState: 'supported', + readinessStage: 'supported', + primaryMode: 'api-backed', onboardingPaths: ['platform-connections'], + canonicalProjections: ['agent', 'app-container', 'storage', 'physical-disk'], + supportFloor: { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'supported', + recovery: 'supported', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + }, uiLabel: 'TrueNAS', uiTone: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-400', aliases: [], @@ -100,7 +191,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'vmware-vsphere', family: 'VMware', governanceState: 'admitted', + readinessStage: 'first-lab-ready', + primaryMode: 'api-backed', onboardingPaths: ['platform-connections'], + canonicalProjections: ['agent', 'vm', 'storage'], + supportFloor: { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'supported', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'read-only', + }, uiLabel: 'vSphere', uiTone: 'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300', aliases: ['vmware'], @@ -111,7 +215,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'unraid', family: 'Unraid', governanceState: 'presentation-only', + readinessStage: 'presentation-only', + primaryMode: 'presentation-only', onboardingPaths: [], + canonicalProjections: [], + supportFloor: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, uiLabel: 'Unraid', uiTone: 'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300', aliases: [], @@ -122,7 +239,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'synology-dsm', family: 'Synology', governanceState: 'presentation-only', + readinessStage: 'presentation-only', + primaryMode: 'presentation-only', onboardingPaths: [], + canonicalProjections: [], + supportFloor: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, uiLabel: 'Synology', uiTone: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300', aliases: [], @@ -133,7 +263,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'microsoft-hyperv', family: 'Hyper-V', governanceState: 'presentation-only', + readinessStage: 'presentation-only', + primaryMode: 'presentation-only', onboardingPaths: [], + canonicalProjections: [], + supportFloor: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, uiLabel: 'Hyper-V', uiTone: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900 dark:text-cyan-300', aliases: ['hyper-v'], @@ -144,7 +287,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'aws', family: 'AWS', governanceState: 'presentation-only', + readinessStage: 'presentation-only', + primaryMode: 'presentation-only', onboardingPaths: [], + canonicalProjections: [], + supportFloor: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, uiLabel: 'AWS', uiTone: 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300', aliases: [], @@ -155,7 +311,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'azure', family: 'Azure', governanceState: 'presentation-only', + readinessStage: 'presentation-only', + primaryMode: 'presentation-only', onboardingPaths: [], + canonicalProjections: [], + supportFloor: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, uiLabel: 'Azure', uiTone: 'bg-sky-100 text-sky-700 dark:bg-sky-900 dark:text-sky-300', aliases: [], @@ -166,7 +335,20 @@ export const PLATFORM_SUPPORT_MANIFEST = { id: 'gcp', family: 'GCP', governanceState: 'presentation-only', + readinessStage: 'presentation-only', + primaryMode: 'presentation-only', onboardingPaths: [], + canonicalProjections: [], + supportFloor: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, uiLabel: 'GCP', uiTone: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300', aliases: [], @@ -305,6 +487,196 @@ export const SOURCE_PLATFORM_FAMILY = { azure: 'Azure', gcp: 'GCP', } as const; +export const SOURCE_PLATFORM_READINESS_STAGE = { + agent: 'supported', + docker: 'supported', + kubernetes: 'supported', + 'proxmox-pve': 'supported', + 'proxmox-pbs': 'supported', + 'proxmox-pmg': 'supported', + truenas: 'supported', + 'vmware-vsphere': 'first-lab-ready', + unraid: 'presentation-only', + 'synology-dsm': 'presentation-only', + 'microsoft-hyperv': 'presentation-only', + aws: 'presentation-only', + azure: 'presentation-only', + gcp: 'presentation-only', +} as const; +export const SOURCE_PLATFORM_PRIMARY_MODE = { + agent: 'agent-backed', + docker: 'agent-backed', + kubernetes: 'agent-backed', + 'proxmox-pve': 'api-backed', + 'proxmox-pbs': 'api-backed', + 'proxmox-pmg': 'api-backed', + truenas: 'api-backed', + 'vmware-vsphere': 'api-backed', + unraid: 'presentation-only', + 'synology-dsm': 'presentation-only', + 'microsoft-hyperv': 'presentation-only', + aws: 'presentation-only', + azure: 'presentation-only', + gcp: 'presentation-only', +} as const; +export const SOURCE_PLATFORM_CANONICAL_PROJECTIONS = { + agent: ['agent', 'storage', 'physical-disk'], + docker: ['agent', 'app-container', 'docker-service'], + kubernetes: ['k8s-cluster', 'k8s-node', 'pod', 'k8s-deployment'], + 'proxmox-pve': ['agent', 'vm', 'system-container', 'storage', 'ceph', 'physical-disk'], + 'proxmox-pbs': ['pbs', 'storage'], + 'proxmox-pmg': ['pmg'], + truenas: ['agent', 'app-container', 'storage', 'physical-disk'], + 'vmware-vsphere': ['agent', 'vm', 'storage'], + unraid: [], + 'synology-dsm': [], + 'microsoft-hyperv': [], + aws: [], + azure: [], + gcp: [], +} as const; +export const SOURCE_PLATFORM_SUPPORT_FLOOR = { + agent: { + setup: 'supported', + visibility: 'supported', + workloads: 'n/a', + storage: 'supported', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + }, + docker: { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'n/a', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + }, + kubernetes: { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'n/a', + recovery: 'supported', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + }, + 'proxmox-pve': { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'supported', + recovery: 'supported', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'augmentation-only', + }, + 'proxmox-pbs': { + setup: 'supported', + visibility: 'supported', + workloads: 'n/a', + storage: 'supported', + recovery: 'supported', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'read-only', + }, + 'proxmox-pmg': { + setup: 'supported', + visibility: 'supported', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'read-only', + }, + truenas: { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'supported', + recovery: 'supported', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'supported', + }, + 'vmware-vsphere': { + setup: 'supported', + visibility: 'supported', + workloads: 'supported', + storage: 'supported', + recovery: 'n/a', + alerts: 'supported', + assistantRead: 'supported', + assistantControl: 'read-only', + }, + unraid: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, + 'synology-dsm': { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, + 'microsoft-hyperv': { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, + aws: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, + azure: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, + gcp: { + setup: 'n/a', + visibility: 'n/a', + workloads: 'n/a', + storage: 'n/a', + recovery: 'n/a', + alerts: 'n/a', + assistantRead: 'n/a', + assistantControl: 'n/a', + }, +} as const; export const SOURCE_PLATFORM_ONBOARDING_PATHS = { agent: ['install-workspace'], docker: ['install-workspace'], @@ -401,6 +773,12 @@ export const SOURCE_PLATFORM_STORAGE_FAMILY = { } as const; export type PlatformGovernanceState = (typeof SOURCE_PLATFORM_MANIFEST_ENTRIES)[number]['governanceState']; +export type PlatformReadinessStage = + (typeof SOURCE_PLATFORM_MANIFEST_ENTRIES)[number]['readinessStage']; +export type PlatformPrimaryMode = (typeof SOURCE_PLATFORM_MANIFEST_ENTRIES)[number]['primaryMode']; +export type PlatformSupportFloor = + (typeof SOURCE_PLATFORM_MANIFEST_ENTRIES)[number]['supportFloor']; +export type PlatformSupportFloorValue = PlatformSupportFloor[keyof PlatformSupportFloor]; export type GeneratedSourcePlatformOnboardingPath = (typeof SOURCE_PLATFORM_ONBOARDING_PATH_KEYS)[number]; export type SourcePlatformStorageFamily = diff --git a/frontend-modern/src/utils/platformSupportManifest.ts b/frontend-modern/src/utils/platformSupportManifest.ts index e9fe47214..54048930d 100644 --- a/frontend-modern/src/utils/platformSupportManifest.ts +++ b/frontend-modern/src/utils/platformSupportManifest.ts @@ -6,6 +6,7 @@ import { PLATFORM_SUPPORT_MANIFEST_SOURCE, PRESENTATION_ONLY_PLATFORM_IDS, PLATFORM_SUPPORT_MANIFEST, + SOURCE_PLATFORM_CANONICAL_PROJECTIONS, SOURCE_PLATFORM_ONBOARDING_PATH_KEYS, SOURCE_PLATFORM_ONBOARDING_PATHS, SOURCE_PLATFORM_PRESENTATION, @@ -14,11 +15,18 @@ import { SOURCE_PLATFORM_DISPLAY_TOKENS, SOURCE_PLATFORM_FAMILY, SOURCE_PLATFORM_MANIFEST_ENTRIES, + SOURCE_PLATFORM_PRIMARY_MODE, + SOURCE_PLATFORM_READINESS_STAGE, SOURCE_PLATFORM_STORAGE_FAMILY, + SOURCE_PLATFORM_SUPPORT_FLOOR, SUPPORTED_PLATFORM_IDS, type GeneratedKnownSourcePlatform, type GeneratedSourcePlatformOnboardingPath, type GeneratedSourcePlatformManifestEntry, + type PlatformPrimaryMode, + type PlatformReadinessStage, + type PlatformSupportFloor, + type PlatformSupportFloorValue, type PlatformGovernanceState, type SourcePlatformFamily, type SourcePlatformStorageFamily, @@ -28,6 +36,10 @@ export type SourcePlatformManifestEntry = GeneratedSourcePlatformManifestEntry; export type { GeneratedKnownSourcePlatform, GeneratedSourcePlatformOnboardingPath as SourcePlatformOnboardingPath, + PlatformPrimaryMode, + PlatformReadinessStage, + PlatformSupportFloor, + PlatformSupportFloorValue, PlatformGovernanceState, SourcePlatformFamily, SourcePlatformStorageFamily, @@ -46,6 +58,7 @@ export { PLATFORM_TYPE_KEYS, PRESENTATION_ONLY_PLATFORM_IDS, PLATFORM_SUPPORT_MANIFEST, + SOURCE_PLATFORM_CANONICAL_PROJECTIONS, SOURCE_PLATFORM_ONBOARDING_PATH_KEYS, SOURCE_PLATFORM_ONBOARDING_PATHS, SOURCE_PLATFORM_PRESENTATION, @@ -54,6 +67,9 @@ export { SOURCE_PLATFORM_DISPLAY_TOKENS, SOURCE_PLATFORM_FAMILY, SOURCE_PLATFORM_MANIFEST_ENTRIES, + SOURCE_PLATFORM_PRIMARY_MODE, + SOURCE_PLATFORM_READINESS_STAGE, + SOURCE_PLATFORM_SUPPORT_FLOOR, SUPPORTED_PLATFORM_IDS, }; @@ -76,6 +92,38 @@ export const getSourcePlatformStorageFamily = ( return SOURCE_PLATFORM_STORAGE_FAMILY[manifestPlatform.id]; }; +export const getSourcePlatformReadinessStage = ( + value: string | null | undefined, +): PlatformReadinessStage | null => { + const manifestPlatform = getSourcePlatformManifestEntry(value); + if (!manifestPlatform) return null; + return SOURCE_PLATFORM_READINESS_STAGE[manifestPlatform.id]; +}; + +export const getSourcePlatformPrimaryMode = ( + value: string | null | undefined, +): PlatformPrimaryMode | null => { + const manifestPlatform = getSourcePlatformManifestEntry(value); + if (!manifestPlatform) return null; + return SOURCE_PLATFORM_PRIMARY_MODE[manifestPlatform.id]; +}; + +export const getSourcePlatformCanonicalProjections = ( + value: string | null | undefined, +): readonly string[] => { + const manifestPlatform = getSourcePlatformManifestEntry(value); + if (!manifestPlatform) return []; + return SOURCE_PLATFORM_CANONICAL_PROJECTIONS[manifestPlatform.id]; +}; + +export const getSourcePlatformSupportFloor = ( + value: string | null | undefined, +): PlatformSupportFloor | null => { + const manifestPlatform = getSourcePlatformManifestEntry(value); + if (!manifestPlatform) return null; + return SOURCE_PLATFORM_SUPPORT_FLOOR[manifestPlatform.id]; +}; + export const getSourcePlatformOnboardingPaths = ( value: string | null | undefined, ): readonly GeneratedSourcePlatformOnboardingPath[] => { diff --git a/internal/mock/platform_support_contract_test.go b/internal/mock/platform_support_contract_test.go index 5bac86862..601d19c78 100644 --- a/internal/mock/platform_support_contract_test.go +++ b/internal/mock/platform_support_contract_test.go @@ -26,14 +26,18 @@ type platformSupportManifest struct { } type platformSupportManifestEntry struct { - ID string `json:"id"` - GovernanceState string `json:"governance_state"` - OnboardingPaths []string `json:"onboarding_paths"` - UILabel string `json:"ui_label"` - UITone string `json:"ui_tone"` - Aliases []string `json:"aliases"` - DisplayTokens []string `json:"display_tokens"` - StorageFamily string `json:"storage_family"` + ID string `json:"id"` + GovernanceState string `json:"governance_state"` + ReadinessStage string `json:"readiness_stage"` + PrimaryMode string `json:"primary_mode"` + OnboardingPaths []string `json:"onboarding_paths"` + CanonicalProjections []string `json:"canonical_projections"` + SupportFloor map[string]string `json:"support_floor"` + UILabel string `json:"ui_label"` + UITone string `json:"ui_tone"` + Aliases []string `json:"aliases"` + DisplayTokens []string `json:"display_tokens"` + StorageFamily string `json:"storage_family"` } func TestPlatformSupportManifestMatchesSupportModel(t *testing.T) { @@ -126,6 +130,22 @@ func TestVMwareFixturesRemainAdmittedButNotSupported(t *testing.T) { 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") } + 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.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) + } + if got := vmwareManifest.SupportFloor["recovery"]; got != "n/a" { + t.Fatalf("vmware recovery support floor = %q, want n/a", got) + } + if got := vmwareManifest.SupportFloor["assistant_control"]; got != "read-only" { + t.Fatalf("vmware assistant control support floor = %q, want read-only", got) + } graph := buildFixtureGraph(DefaultConfig, time.Date(2026, time.April, 10, 12, 0, 0, 0, time.UTC)) if len(graph.PlatformFixtures.VMware.Hosts) == 0 { @@ -475,6 +495,18 @@ func manifestPlatformOnboardingPaths(manifest platformSupportManifest) map[strin return rows } +func requireManifestPlatform(t *testing.T, manifest platformSupportManifest, platformID string) platformSupportManifestEntry { + t.Helper() + + for _, platform := range manifest.Platforms { + if platform.ID == platformID { + return platform + } + } + t.Fatalf("expected platform support manifest entry for %s", platformID) + return platformSupportManifestEntry{} +} + func requireNonEmptyPlatformList(t *testing.T, platforms []string, label string) []string { t.Helper() diff --git a/scripts/release_control/generate_platform_support_frontend_module.py b/scripts/release_control/generate_platform_support_frontend_module.py index f8aa6077a..30810b354 100755 --- a/scripts/release_control/generate_platform_support_frontend_module.py +++ b/scripts/release_control/generate_platform_support_frontend_module.py @@ -20,8 +20,21 @@ DEFAULT_GENERIC_PRESENTATION = { "tone": "bg-surface-alt text-base-content", } VALID_GOVERNANCE_STATES = {"supported", "admitted", "presentation-only"} +VALID_PRIMARY_MODES = {"api-backed", "agent-backed", "hybrid", "presentation-only"} +VALID_READINESS_STAGES = {"supported", "first-lab-ready", "presentation-only"} VALID_STORAGE_FAMILIES = {"onprem", "container", "virtualization", "cloud"} VALID_ONBOARDING_PATHS = {"install-workspace", "platform-connections"} +SUPPORT_FLOOR_FIELDS = ( + "setup", + "visibility", + "workloads", + "storage", + "recovery", + "alerts", + "assistant_read", + "assistant_control", +) +VALID_SUPPORT_FLOOR_VALUES = {"supported", "augmentation-only", "read-only", "n/a"} def require_dict(value: Any, label: str) -> dict[str, Any]: @@ -124,6 +137,61 @@ def normalize_manifest(raw_manifest: dict[str, Any]) -> dict[str, Any]: f"{governance_state} platform {platform_id} must declare at least one onboarding path" ) + primary_mode = require_string(record.get("primary_mode"), f"platforms[{index}].primary_mode") + if primary_mode not in VALID_PRIMARY_MODES: + raise ValueError( + f"expected platforms[{index}].primary_mode to be one of {sorted(VALID_PRIMARY_MODES)}" + ) + readiness_stage = require_string( + record.get("readiness_stage"), f"platforms[{index}].readiness_stage" + ) + if readiness_stage not in VALID_READINESS_STAGES: + raise ValueError( + f"expected platforms[{index}].readiness_stage to be one of " + f"{sorted(VALID_READINESS_STAGES)}" + ) + if governance_state == "supported" and readiness_stage != "supported": + raise ValueError(f"supported platform {platform_id} must use readiness_stage supported") + if governance_state == "admitted" and readiness_stage == "supported": + raise ValueError(f"admitted platform {platform_id} must not use readiness_stage supported") + if governance_state == "presentation-only": + if readiness_stage != "presentation-only": + raise ValueError( + f"presentation-only platform {platform_id} must use readiness_stage presentation-only" + ) + if primary_mode != "presentation-only": + raise ValueError( + f"presentation-only platform {platform_id} must use primary_mode presentation-only" + ) + + canonical_projections = unique_preserve_order( + require_string_list( + record.get("canonical_projections"), + f"platforms[{index}].canonical_projections", + ) + ) + if governance_state != "presentation-only" and not canonical_projections: + raise ValueError(f"{governance_state} platform {platform_id} must declare projections") + if governance_state == "presentation-only" and canonical_projections: + raise ValueError(f"presentation-only platform {platform_id} must not declare projections") + + support_floor_record = require_dict(record.get("support_floor"), f"platforms[{index}].support_floor") + support_floor: dict[str, str] = {} + for field in SUPPORT_FLOOR_FIELDS: + value = require_string( + support_floor_record.get(field), + f"platforms[{index}].support_floor.{field}", + ) + if value not in VALID_SUPPORT_FLOOR_VALUES: + raise ValueError( + f"expected platforms[{index}].support_floor.{field} to be one of " + f"{sorted(VALID_SUPPORT_FLOOR_VALUES)}" + ) + camel_field = field.split("_")[0] + "".join(part.title() for part in field.split("_")[1:]) + support_floor[camel_field] = value + if governance_state == "presentation-only" and any(value != "n/a" for value in support_floor.values()): + raise ValueError(f"presentation-only platform {platform_id} support floor must be n/a") + aliases = unique_preserve_order( [ require_lowercase_identifier(alias, f"platforms[{index}].aliases") @@ -146,7 +214,11 @@ def normalize_manifest(raw_manifest: dict[str, Any]) -> dict[str, Any]: "id": platform_id, "family": require_string(record.get("family"), f"platforms[{index}].family"), "governanceState": governance_state, + "readinessStage": readiness_stage, + "primaryMode": primary_mode, "onboardingPaths": onboarding_paths, + "canonicalProjections": canonical_projections, + "supportFloor": support_floor, "uiLabel": require_string(record.get("ui_label"), f"platforms[{index}].ui_label"), "uiTone": require_string(record.get("ui_tone"), f"platforms[{index}].ui_tone"), "aliases": aliases, @@ -199,6 +271,18 @@ def normalize_manifest(raw_manifest: dict[str, Any]) -> dict[str, Any]: family_by_id = { platform["id"]: platform["family"] for platform in platforms } + readiness_stage_by_id = { + platform["id"]: platform["readinessStage"] for platform in platforms + } + primary_mode_by_id = { + platform["id"]: platform["primaryMode"] for platform in platforms + } + canonical_projections_by_id = { + platform["id"]: platform["canonicalProjections"] for platform in platforms + } + support_floor_by_id = { + platform["id"]: platform["supportFloor"] for platform in platforms + } onboarding_paths_by_id = { platform["id"]: platform["onboardingPaths"] for platform in platforms } @@ -237,6 +321,10 @@ def normalize_manifest(raw_manifest: dict[str, Any]) -> dict[str, Any]: "displayTokens": display_tokens, "onboardingPathKeys": onboarding_path_keys, "familyById": family_by_id, + "readinessStageById": readiness_stage_by_id, + "primaryModeById": primary_mode_by_id, + "canonicalProjectionsById": canonical_projections_by_id, + "supportFloorById": support_floor_by_id, "onboardingPathsById": onboarding_paths_by_id, "presentation": {**presentation, "generic": DEFAULT_GENERIC_PRESENTATION}, "storageFamilyById": storage_family_by_id, @@ -284,11 +372,22 @@ def render_module(normalized: dict[str, Any], manifest_hash: str) -> str: render_const("SOURCE_PLATFORM_DISPLAY_TOKENS", normalized["displayTokens"]), render_const("SOURCE_PLATFORM_ONBOARDING_PATH_KEYS", normalized["onboardingPathKeys"]), render_const("SOURCE_PLATFORM_FAMILY", normalized["familyById"]), + render_const("SOURCE_PLATFORM_READINESS_STAGE", normalized["readinessStageById"]), + render_const("SOURCE_PLATFORM_PRIMARY_MODE", normalized["primaryModeById"]), + render_const("SOURCE_PLATFORM_CANONICAL_PROJECTIONS", normalized["canonicalProjectionsById"]), + render_const("SOURCE_PLATFORM_SUPPORT_FLOOR", normalized["supportFloorById"]), render_const("SOURCE_PLATFORM_ONBOARDING_PATHS", normalized["onboardingPathsById"]), render_const("SOURCE_PLATFORM_PRESENTATION", normalized["presentation"]), render_const("SOURCE_PLATFORM_STORAGE_FAMILY", normalized["storageFamilyById"]), "export type PlatformGovernanceState =\n" " (typeof SOURCE_PLATFORM_MANIFEST_ENTRIES)[number]['governanceState'];\n", + "export type PlatformReadinessStage =\n" + " (typeof SOURCE_PLATFORM_MANIFEST_ENTRIES)[number]['readinessStage'];\n", + "export type PlatformPrimaryMode =\n" + " (typeof SOURCE_PLATFORM_MANIFEST_ENTRIES)[number]['primaryMode'];\n", + "export type PlatformSupportFloor =\n" + " (typeof SOURCE_PLATFORM_MANIFEST_ENTRIES)[number]['supportFloor'];\n", + "export type PlatformSupportFloorValue = PlatformSupportFloor[keyof PlatformSupportFloor];\n", "export type GeneratedSourcePlatformOnboardingPath =\n" " (typeof SOURCE_PLATFORM_ONBOARDING_PATH_KEYS)[number];\n", "export type SourcePlatformStorageFamily =\n" diff --git a/scripts/release_control/subsystem_lookup_test.py b/scripts/release_control/subsystem_lookup_test.py index 03487a23a..68bac64f4 100644 --- a/scripts/release_control/subsystem_lookup_test.py +++ b/scripts/release_control/subsystem_lookup_test.py @@ -84,7 +84,7 @@ class SubsystemLookupTest(unittest.TestCase): {"v6-rc-cut", "v6-rc-stabilization", "v6-ga-promotion", "v6-product-lane-expansion"}, ) self.assertEqual(result["scope"]["control_plane_repo"], "pulse") - self.assertEqual(result["status_summary"]["lane_count"], 20) + self.assertEqual(result["status_summary"]["lane_count"], 21) file_entry = result["files"][0] matches = {match["subsystem"] for match in file_entry["matches"]}