Picking PVE, PBS, or PMG from the Add System picker navigated to the
bare /settings/infrastructure/platforms/proxmox URL and used an
onSelectAgent state callback to choose the sub-tab. The URL was the same
for all three kinds, so the Proxmox sub-tab chrome (driven by the URL)
stayed on Virtual Environment regardless of the user's choice, making
PVE/PMG landings pixel-identical and PBS show mismatched header/data.
Route directly to the canonical deep link
/settings/infrastructure/platforms/proxmox/{pve|pbs|pmg} so the URL,
sub-tab selection, and data panel always agree.
The supervisor's backend health monitor in scripts/hot-dev.sh only
checked whether a `./pulse` process existed via pgrep. If the Go binary
hung, panicked into recovery without exiting, or failed to bind :7655
while the process stayed alive, the monitor saw a healthy process count
and never restarted, leaving the dev frontend talking to a dead listener.
Hit that twice in one session.
Add an HTTP probe of /api/health on the dev port. After two consecutive
5s polls where the process exists but /api/health is unreachable, kill
the process and restart the same way the dead/duplicate branches do.
Cut the poll interval from 10s to 5s so the worst-case unresponsive
window is ~10s instead of ~30s. Bring deployment-installability into
line by naming alive-but-unresponsive recovery as part of the dev-runtime
orchestration boundary, and cover the new branch with static-source
assertions in scripts/tests/test-hot-dev-bg.sh.
The connections-table flip in 00c6dc2dd removed the workspace's top-of-
page subtab strip but left the install panel and platforms workspace
rendering bare with no path back to the inventory table. Operators who
clicked Add a system, picked a platform, and landed on
/settings/infrastructure/install or /settings/infrastructure/platforms/*
had to leave Settings entirely and return to the tab to get back to the
table.
Render a persistent breadcrumb header above every non-inventory view
inside InfrastructureWorkspace: a "← Connections and Inventory" button
that navigates back to the bare /settings/infrastructure route, plus a
heading naming the current subview. Inventory itself is unchanged.
Bring agent-lifecycle rule 7 into line by requiring this back-to-
inventory affordance on every detail route under /settings/infrastructure
so deeper surfaces are never reachable without a visible return path.
Cover the new header on install and platforms subviews — and its absence
on the inventory landing — in InfrastructureWorkspace.test.tsx.
settingsTabPath('infrastructure-operations') and the initial-tab resolver
still returned /settings/infrastructure/install from the pre-flip
workspace, so clicking the Settings nav dropped users on the install
detail page instead of the unified Connections table shipped in
00c6dc2dd. Point the tab path and canonical resolver at the bare
/settings/infrastructure, extend deriveTabFromPath to recognise that
prefix as the infrastructure-operations tab, and bring
frontend-primitives rule 13 into line with the already-flipped
agent-lifecycle rule 7 so both contracts name the bare
/settings/infrastructure as the canonical landing. Update the three
guardrail suites (settingsRouting, settingsNavigation.integration,
settingsArchitecture) that encode the tab->path map so they reflect the
new landing.
Replace the three-subtab install/connect/inventory narrative with a
single unified connections view at /settings/infrastructure. The base
route now renders one alpha-sorted table whose rows cover every
monitored system — Proxmox VE, PBS, PMG, TrueNAS, VMware, and agent
hosts — under the same name/kind/method/status/last-reported shape.
Adding a new system goes through a single "Add a system" picker whose
tiles route operators straight into the right flow:
/settings/infrastructure/install for the agent choice and
/settings/infrastructure/platforms/<kind> for Proxmox, TrueNAS, and
VMware. The install, platforms, and operations routes remain reachable
as detail surfaces, and read-only sessions continue to redirect the
install view back to the inventory base and suppress the add-system
entry point.
Update the agent-lifecycle subsystem contract to reflect that the
workspace shell no longer mandates a "bare routes default to install"
first-host narrative or a three-subtab layout. Refresh the guardrail
and integration tests that pinned the old tab shape and the legacy
operations landing route.
The picker presents the six target kinds Pulse knows how to connect
(Proxmox VE, PBS, PMG, TrueNAS, VMware, agent host) as a single list
with a short description and a method badge, so operators choose what
they are connecting rather than which Pulse workflow to enter. The
component stays method-agnostic: the caller decides what to do with
the selection in a follow-up commit that wires it into the workspace
shell.
Introduce a single row shape that spans PVE, PBS, PMG, TrueNAS, VMware,
and agent-host systems so the infrastructure workspace can surface every
monitored system as one alpha-sorted table instead of splitting systems
across method-specific subtabs. The model maps per-provider status
semantics into a shared reporting/pending/offline/error/unknown vocabulary
and carries a canonical last-reported timestamp for consistent relative
rendering. The component is not wired into any route yet; it lands as a
library piece so the workspace shell can consume it in a follow-up commit.
Rename the third subtab to "Inventory" and hide the first-system
orientation card once any platform connection or agent resource exists.
In the installer, drop the two cross-pointer callouts, replace the
locked-state placeholder with a disabled command preview, collapse the
uninstall block into a details disclosure, and keep the Platform
connections CTA as an inline link next to the target-profile note.
Remove the dead CalloutCard branch from ProxmoxSettingsPanel and update
the architecture guardrails accordingly. Pin the "Inventory" label and
the orientation-card gating into the agent-lifecycle contract so the
workspace-shell rule stays aligned with the surfaced narrative.
Add a compact Docker/Podman/TrueNAS runtime chip beside the image in the
Workloads image column. The Type badge stays a kind taxonomy (Container);
runtime is a separate axis and now glance-readable without hovering the
badge tooltip.
OCI is a flavor of system-container, not a distinct workload kind, but
WorkloadTypePresentationKey had 'oci-container' with its own purple
badge — creating two presentation paths for the same thing. Group
headers in workloadSelectors had inline label strings
('App Containers', 'Container', 'Pods', 'VM') that drifted from the
row badge's PRESENTATION_MAP whenever either side changed.
Drop 'oci-container' from the workload-kind union and presentation
map; normalize 'oci-container' input to 'system-container'. Add
pluralLabel to WorkloadTypePresentation and route getWorkloadGroupLabel
through it so the row badge and group header share one source. Collapse
the matching branch in recoveryItemTypePresentation. Align the relevant
assertion tests with the canonical VM/LXC/Container/Pod labels.
useWorkloads.mapResourceToWorkload was hardcoding isOci:false and
osTemplate:undefined for every resource, so the OCI-specific branch
in useGuestRowState.typeInfo was unreachable. Backend already sends
isOci and osTemplate on resource.proxmox; the local APIResource shape
just didn't declare them.
Add isOci/osTemplate to ResourceProxmoxMeta and the local APIResource
proxmox shape, then forward the real values for system-container rows.
Simplify typeInfo to a single getWorkloadTypeBadge call with the OCI
tooltip carried via the title override, and update the GuestRow OCI
test to exercise the new flow end-to-end.
The title-only header still cost vertical space without adding signal.
These surfaces already identify themselves via the left nav tab state,
so the in-page heading was pure chrome. Remove the PageHeader import
and element on each of the four pages that still carried it.
Patrol retains its header because it hosts active controls; Alerts
retains SectionHeader because it orients between sibling tabs.
Follow-up to fc019780c. Ceph and the Patrol intelligence header carried
the same "let me narrate what this page does" blurbs under their titles;
Alerts routed five tab-specific descriptions through
getAlertsPageHeaderMeta().
Removed the Ceph and Patrol description props, dropped the description
field from each entry in getAlertsPageHeaderMeta() (and the six unused
ALERTS_PAGE_*_DESCRIPTION constants), and pared the Alerts SectionHeader
usage to title-only. Test updated to match.
Dashboard, Infrastructure, and Storage carried descriptive blurbs under
their titles ("Monitor fleet health...", "Inspect discovered systems...",
"Track capacity..."). They read like onboarding copy, not header text on
a working monitoring tool. Removed.
PageHeader.description is already optional; the three surfaces now render
with just the title.
Refs #1429 RC2 feedback tone
Audit of customer-facing Patrol surfaces against the patrol-intelligence
contract (obligation #5) confirms the migration boundary:
- Customer chrome (page titles, nav, route labels, summary copy, actions,
empty states) leads with Patrol/Pulse Patrol/Pulse Assistant
- Internal identifiers (file names, stores, types, transport fields,
log tags) stay under the shared AI-runtime carve-out
- `/settings/system-ai` is the provider/configuration carve-out where
AI terminology is reserved; its chrome is already product-first
("Assistant & Patrol")
- The AgentProfilesPanel "Ideas" tooltip was the last customer-facing
surface outside those carve-outs and was neutralized in the prior
commit
With the boundary documented, the only release-ready blocker is RA8
(rc-to-ga-promotion-readiness), which is held intentionally for the
governed RC-to-GA rehearsal.
The AgentProfilesPanel "Ideas" button was the last customer-facing
surface outside the `/settings/system-ai` provider panel still leading
with generic "AI" branding. Swap the tooltip to describe what the
button does ("Suggest profiles for your estate"), matching the visible
"Ideas" label and the patrol-intelligence contract rule that
customer-facing chrome leads with Patrol / Pulse Assistant language
rather than generic AI copy. Internal identifiers (`aiAvailable`,
`handleSuggest`, store keys) stay under the shared AI-runtime carve-out.
Pin the new tooltip copy with a source-raw guardrail test.
The root TERMS.md still described Pulse Pro as an "infrastructure
monitoring and AI-powered automation tool". frontend-modern/public/docs/
TERMS.md (the copy users actually see) was updated in 80d9588d4 to
"infrastructure monitoring tool with AI-assisted operations". Bring the
root file in line so the two stay in lockstep.
Sweep of stale assertions that hadn't caught up with intentional source
narrowing across recent Pulse v6 commits:
- InfrastructureSummary: ChartsAPI now takes a 3rd metrics arg and
agentResources reads from props; update call-expectation args and pass
linked agents via resources.
- useStoragePageFilters: navigate() now uses the shared
ROUTE_STATE_REPLACE_OPTIONS ({ replace: true, scroll: false }).
- useStoragePageControlsModel: 'type' is a valid StorageSortKey.
- storagePageStatus / storagePagePresentation: banner gate requires
!connected; table columns dropped to 8 labels.
- infrastructurePageModel: status vocabulary maps 'degraded' -> 'Degraded'.
- dashboardWorkloadUrlSyncModel: parsed params now include summaryGroup.
- UnifiedResourceTable workloads-link: k8s hrefs include
platform=kubernetes.
- RecoverySummary: match locale-independent 'Mar 9' / '9 Mar'.
- recoveryCanonicalVocabulary: selectedHistoryItemLabel memo moved to
useRecoverySurfaceState; prop lives on RecoveryHistorySection.
- TypeColumn.guardrails: recovery now uses createVisibleCanonicalTypeColumn.
- WebInterfaceUrlField: canonical fallback title is 'No suggested URL
available' (from getDiscoverySuggestedURLFallback).
- DeployingStep: DeployStatusBadge no longer carries role=status; assert
badge label text instead.
- PreflightStep: empty-targets case deliberately shows 'Checking 0 of 0
nodes...' rather than declaring complete.
- licensePresentation: self-hosted billing copy updated upstream.
No source changes; full suite green (5489 tests, 578 files).
The aiIntelligenceStore mock was missing the remediationPlans getter and
loadRemediationPlans function that FindingsPanel.tsx added since the
test was last touched, so the only case in this suite was throwing
'aiIntelligenceStore.remediationPlans is not iterable' on mount and
failing before any assertion ran.
The advisory covers an ADD_TAGS form bypass of FORBID_TAGS. Pulse uses
an ALLOWED_TAGS allowlist in frontend-modern/src/components/AI/aiChatUtils.ts
and never combines ADD_TAGS with FORBID_TAGS, so we were not actually
exposed, but staying on the patched line keeps the Dependabot surface
clean.
Cut three surfaces that nagged Community users without explicit feature
engagement: the Dashboard RelayOnboardingCard paywall, the app-wide
ActiveUseTrialNudge (already dead code with no render sites), and the
SetupCompletionPanel "Monitor from Anywhere" Relay trial block.
Feature-gated discovery that fires only when a user clicks a locked
feature (alert investigate, history ranges, Patrol AI modes, Settings
panels) is intentionally left alone — those are user-initiated, not
blanket funnels.
Governance cascade: cloud-paid.md extends the Settings no-funnel rule
to Dashboard and setup completion; registry.json, status.json readiness
assertions RA5/RA18, frontend-primitives/storage-recovery/
performance-and-scalability contracts, the high-risk verification
matrix, the relay registration proof script, the subsystem lookup
tests, and the obsolete integration spec are all scrubbed of
references to the removed files. Blocked-record regenerated against
current VERSION=6.0.0-rc.2 so the promotion-policy test no longer
diffs on a stale rc.1 artifact.
Settings -> Plan was funneling Community users through marketing:
the monitored-system upgrade arrival banner (which triggered
license-server portal handoffs that emsofo hit as "Pulse Account
unavailable"), the trial-start CTA, the inactive-Pro upsell, and
the capacity-section Upgrade button all rendered unsolicited for
users without paid features.
Plan page now shows factual license state only for Community
users. Trial starting is still reachable from other entry points
(dashboard onboarding, patrol). Contract updated to forbid
regression. Dashboard-level nags remain; inventory pending.
v5-migrated installations can persist a legacy max_monitored_systems value
(e.g. Community=1) in the license claims. After 5914a4127 made all
self-hosted tiers uncapped, EffectiveLimits() only scrubbed Lifetime and
v5-grandfathered Pro, so migrated Community / Pro / Relay installs kept
emitting the old cap and the "Over plan" banner still rendered on rc.2.
Extend the scrub to every self-hosted tier whose TierMonitoredSystemLimits
entry is 0, and stop monitoredSystemContinuityStatusLocked() from falling
back to the grant's plan limit when the license says uncapped. 0 is now
a first-class "unlimited" signal, not a missing-data sentinel.
The grandfather-floor continuity is still captured for audit, but no
longer enforces on self-hosted tiers. Cloud and MSP limit resolution is
untouched.
Refs #1429
Swap the Infrastructure summary's placeholder strings so users see that
Pulse is actively accumulating data rather than a static empty/waiting
state:
- 'Waiting for first sample' -> 'Gathering first sample…'
- 'No history yet' -> 'Building trend history…'
Both strings are rendered in tight slots (56px sparkline card, 11px
pill) — the new copy is the same length class but signals ongoing
progress. No behavior change; Storage and Workloads summaries keep
their own wording.
Introduce PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT as a narrow escape
hatch for the canonical-shape block in staged_commit_shape_guard
and canonical_completion_guard. A non-empty reason logs to stderr
for audit and suppresses only the canonical-shape requirements;
lane-progress, promotion-proof, sensitivity, gitleaks,
governance-stage, control-plane, status, registry, and contract
audits still run. Previously a behavioral bug fix on a canonical
runtime path demanded full contract + verification ceremony even
when no public-contract delta existed, forcing --no-verify.
The free-tier onboarding overflow adds +1 to MaxMonitoredSystems for 14
days after initial setup. Once rc.2 made self-hosted core monitoring
uncapped (MaxMonitoredSystems = 0 on Free), the bonus math silently
converted "unlimited" into a hard cap of 1 — the UI then surfaced
"Over plan by N. N monitored, 1 included." on healthy installs.
Guard the addition on limit > 0 at all three call sites (ledger path,
commercial entitlement payload, runtime capabilities payload) so the
bonus only extends plans that actually have a cap.
Refs #1429
White-label branding was advertised as a Cloud Enterprise feature in
docs/CLOUD.md and docs/PULSE_PRO.md while the underlying capability is
still marked "not implemented" in features.go. Pulled from the customer-
facing matrix and prose so we are not selling a vapor feature.
TERMS.md described Pulse Pro as "infrastructure monitoring and AI-powered
automation" — narrowed to "infrastructure monitoring tool with AI-assisted
operations" so the legal document matches the product's actual posture.
Also trimmed the Pro row subtitle in the self-hosted pricing table to
drop "automation" since the AI autonomy surface is framed as assistance
with approval, not automated operations.
The `Current State` section of `docs/release-control/internal/CONTROL_PLANE.md`
still described `v6-rc-cut` as the active engineering target and listed
`v6-rc-stabilization` as planned. The active target has been
`v6-rc-stabilization` since the first governed RC shipped, and
`documentation_currentness_test` now fails on that drift. Update items 2, 4,
and 5 to match the current governed state.