Commit graph

4987 commits

Author SHA1 Message Date
rcourtman
53a9339e4e Add infrastructure workspace peer navigation 2026-04-18 19:09:06 +01:00
rcourtman
90c5c63ced Separate infrastructure subviews from systems ledger 2026-04-18 18:52:11 +01:00
rcourtman
d78bc64583 Recenter infrastructure settings on monitored systems 2026-04-18 18:46:25 +01:00
rcourtman
8883e36f0b Collapse infrastructure settings to ledger-first workspace 2026-04-18 17:13:34 +01:00
rcourtman
847d09d17b Unify settings infrastructure ledger 2026-04-18 14:54:53 +01:00
rcourtman
a6145510d0 Route Proxmox picker choices to kind-scoped platforms URL
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.
2026-04-18 13:15:46 +01:00
rcourtman
b666d131b0 Migrate Docker integrations to maintained Moby modules 2026-04-18 11:40:38 +01:00
rcourtman
35429774b4 Probe /api/health in hot-dev backend monitor
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.
2026-04-18 10:56:33 +01:00
rcourtman
e6b0d47bd6 Gate Docker mutations on authz-plugin posture 2026-04-18 10:45:25 +01:00
rcourtman
8d0aec6e4d Add back-to-inventory header on infrastructure subviews
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.
2026-04-18 10:43:38 +01:00
rcourtman
2dc4e43033 Wire Connections & Inventory nav to the unified table
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.
2026-04-18 10:27:04 +01:00
rcourtman
fb7b9b01fc Update pinned Go toolchain to 1.25.9 2026-04-18 10:04:34 +01:00
rcourtman
f0b37dbfe1 Document remediation execute approval contract 2026-04-18 09:57:02 +01:00
rcourtman
5ebb3d9952 Harden session and setup token auth flows 2026-04-18 00:06:50 +01:00
rcourtman
00c6dc2dd4 Collapse infrastructure workspace onto one connections table
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.
2026-04-18 00:00:34 +01:00
rcourtman
49c8c84387 Add system-picker dialog for target-first onboarding
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.
2026-04-17 23:48:02 +01:00
rcourtman
1d66d62c3e Add unified connections table primitive
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.
2026-04-17 23:45:07 +01:00
rcourtman
280b3dd5f7 Distribute infrastructure full-width columns 2026-04-17 23:39:19 +01:00
rcourtman
446a70ee69 Trim infrastructure workspace onboarding papercuts
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.
2026-04-17 23:37:15 +01:00
rcourtman
beb143aade Fix missing utility navigation icons 2026-04-17 23:17:38 +01:00
rcourtman
0721669067 Surface container runtime as a chip next to the image name
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.
2026-04-17 23:05:13 +01:00
rcourtman
5d75d562f6 Collapse OCI as a workload kind and unify group-header labels
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.
2026-04-17 22:49:28 +01:00
rcourtman
0756ae9279 Forward real Proxmox OCI metadata into WorkloadGuest
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.
2026-04-17 22:48:51 +01:00
rcourtman
ba9589f091 Fix hot-dev mock mode env contract 2026-04-17 20:49:36 +01:00
rcourtman
00da144dd8 Remove v6 RC banner from app shell 2026-04-17 20:06:57 +01:00
rcourtman
4e4fcb5dbe Move operations tools into settings support 2026-04-17 20:03:54 +01:00
rcourtman
3a5f36da19 Retain tab data across recovery and patrol revisits 2026-04-17 19:38:43 +01:00
rcourtman
deb701e340 Align patrol page shell with utility header stack 2026-04-17 19:11:53 +01:00
rcourtman
2d0784ca61 Stabilize recovery and patrol tab loading shells 2026-04-17 19:10:06 +01:00
rcourtman
df06fe84b2 Normalize alerts and patrol page headers 2026-04-17 18:47:02 +01:00
rcourtman
50a7d73293 Add workloads column layout regression 2026-04-17 18:35:34 +01:00
rcourtman
2f8a91d7a6 Rebalance runtime table columns 2026-04-17 18:10:00 +01:00
rcourtman
14b092ce9e frontend: drop PageHeader from Dashboard/Infrastructure/Storage/Ceph
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.
2026-04-17 18:03:14 +01:00
rcourtman
393521f5eb Strip explanatory subtitles from Ceph, Patrol, and Alerts headers
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.
2026-04-17 17:57:32 +01:00
rcourtman
fc019780ce Strip explanatory subtitles from primary page headers
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
2026-04-17 17:54:46 +01:00
rcourtman
865fa45ff2 Resolve patrol-product-language-ai-boundary decision
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.
2026-04-17 16:39:49 +01:00
rcourtman
bece20e7b4 Neutralize AI-branded tooltip on agent profile suggestions
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.
2026-04-17 16:37:39 +01:00
rcourtman
ade98642a9 Align root TERMS.md with shipped docs copy
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.
2026-04-17 15:47:15 +01:00
rcourtman
12b6d93a2a Realign vitest suites with canonical frontend sources
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).
2026-04-17 15:46:45 +01:00
rcourtman
29d0e87dc7 Fix FindingsPanel.links test drift on remediation-plan store surface
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.
2026-04-17 14:58:18 +01:00
rcourtman
a2bd85d806 Bump dompurify 3.3.3 -> 3.4.0 to clear GHSA-39q2-94rc-95cp
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.
2026-04-17 14:55:58 +01:00
rcourtman
85191a9051 Remove blanket Community-visible upgrade marketing surfaces
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.
2026-04-17 14:49:28 +01:00
rcourtman
d35762b029 Remove Community-user upgrade CTAs from Settings plan page
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.
2026-04-17 14:18:06 +01:00
rcourtman
943389827f Scrub stale monitored-system caps on self-hosted uncapped tiers
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
2026-04-17 13:52:02 +01:00
rcourtman
6fced072d3 Clarify trend empty-state copy on Infrastructure summary
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.
2026-04-17 12:49:25 +01:00
rcourtman
675c47d971 Add contract-neutral bypass to staged shape guard
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.
2026-04-17 12:24:01 +01:00
rcourtman
f2d5892aa5 Skip onboarding overflow bonus on uncapped plans
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
2026-04-17 12:16:03 +01:00
rcourtman
80d9588d49 Remove unimplemented white-label claims and fix TOS product description
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.
2026-04-17 10:50:00 +01:00
rcourtman
6ec64c55ed Fix stale v6-rc-cut active-target reference in control-plane doc
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.
2026-04-16 22:57:37 +01:00
rcourtman
b19991f4ae Fix published release body rendering 2026-04-16 22:20:17 +01:00