Remove ownership and proof-policy references to Proxmox backup presentation files that were retired by the guest-centric backup surface, keeping the storage-recovery contract aligned with tracked runtime paths.
MULTI_TENANT.md documented org/member/sharing management but never the MSP
workflow that ties it together: org per client, an org-bound agent:report
token that auto-routes each client's agents into their isolated org, optional
per-client node aliasing via --hostname/PULSE_HOSTNAME, optional split-port
agent ingest, and per-org alert routing to ConnectWise. Also clarifies that
one multi-tenant instance covers all client orgs (no per-client Pro license).
The capabilities already existed; this closes the discoverability gap two MSP
leads hit while evaluating.
Root fix for the recurring class behind issue #1470. The served /install.sh and
/install.ps1 endpoints existed to hand out the unified AGENT installer, but their
GitHub fallback fetched the top-level install.sh release asset, which since
49412357a is the SERVER installer. Prior commits made that fallback unreachable
in normal deployments (deploy the sidecars; serve the local script even when
unsigned), but the endpoint was still structurally capable of serving the wrong
script in the no-local-bundle case.
The agent installer is a per-build artifact bundled into every release tarball and
Docker image, not a release asset, so the endpoint has no business proxying a
release asset at all. Remove proxyInstallScriptFromGitHub and its
installScriptReleaseAssetURL wrapper. handleDownloadInstallScriptCommon now serves
the locally bundled agent installer (signed when sidecars are present, unsigned
otherwise) or fails closed with 503 when no bundled script exists. Serving the
SERVER installer at this endpoint is now structurally impossible, not merely
unreachable.
The shared version-pinning (releaseAssetTag/releaseAssetURL) and installScriptClient
remain for the agent-BINARY download proxy, which legitimately fetches published
release assets; its version-pinning stays covered by the agentBinaryReleaseAssetURL
contract tests.
Replace the obsolete install-script proxy tests with fail-closed assertions
(including a guard that the endpoint makes no outbound call), drop the four
installScriptReleaseAssetURL contract tests, and revise the four subsystem
contracts that pinned the install-script fallback transport (api-contracts items
8 and 27, agent-lifecycle item 14, storage-recovery item 14, plus the
deployment-installability note) to state that install scripts are served local or
fail closed with no GitHub fallback.
The "Install on Linux/Windows" wizard does `curl -fsSL <server>/install.sh |
bash -s -- --url ...` and never verifies the response signature headers (curl|bash
discards them). But for published releases handleDownloadInstallScriptCommon
proxied the top-level GitHub install.sh asset whenever the local agent installer
lacked its .sig/.sshsig sidecars, and since 49412357a that asset is the SERVER
installer, which rejects --url. Every install missing the sidecars served the
wrong script. The companion deploy_agent_scripts fix deploys the sidecars for new
installs, but existing boxes stay broken until they redeploy.
Serve the locally bundled agent installer when its signatures are absent instead
of proxying. An unsigned-but-correct local script beats a signed-but-wrong proxied
one when nothing verifies the headers, and this retroactively fixes already-deployed
boxes the moment they get the new binary. The proxy now runs only when no local
installer is bundled at all, so the endpoint can no longer hand the agent wizard a
server installer in any reachable deployment state. New installs still ship the
sidecars and are served signed.
Revise the install-script signature/fallback contract this changes, across the
three subsystems that pin it (api-contracts item 8, agent-lifecycle item 14,
storage-recovery item 14) plus the deployment-installability note, to state that
the served endpoint serves the agent installer with correctness outranking
signature presence. Add a handler guard asserting a published-release server with
a present-but-unsigned local installer serves it locally and does not proxy.
The running server serves the unified AGENT installer at its /install.sh
endpoint, which the "Install on Linux" wizard fetches with --url/--token-file.
For published releases handleDownloadInstallScriptCommon only serves the local
/opt/pulse/scripts/install.sh when its .sig and .sshsig sidecars sit next to it;
otherwise it proxies the top-level GitHub install.sh asset. Since 49412357a that
asset is the SERVER installer (LXC/systemd quickstart, --version), so the proxy
now hands the agent wizard a server installer that rejects --url with
"Unknown option".
deploy_agent_scripts copied only the .sh/.ps1 files, never the sidecars, so every
published-release LXC/systemd install always hit the proxy and served the wrong
script. The Docker image already deploys both sidecars (Dockerfile); LXC/systemd
did not. This was latent across rc.1-rc.5 (the proxied asset was the agent
installer then) and surfaced in rc.6 once the asset flipped to the server
installer.
Deploy the .sig/.sshsig sidecars alongside each script so the endpoint serves
the local agent installer and never falls back to the server-installer asset.
Add a behavioral guard that runs deploy_agent_scripts and asserts the sidecars
land next to the served script, and document the served-endpoint sidecar
requirement in the deployment-installability contract.
Prefer QEMU guest-agent MemAvailable when Proxmox reports saturated VM memory without guest free fields.
Add regression coverage for the issue #1319 Windows fsinfo volume payload so usable C/E/F volumes remain counted while System Reserved partitions are skipped.
Refs #1319
PULSE_AGENT_INGEST_PORT existed in code but was absent from operator-facing
docs, so the network-isolation feature (dedicated agent-ingest port serving
only /api/agents/*, firewalled from the web UI/management API) was
undiscoverable. Adds it to CONFIGURATION.md (env-var row + Split-Port Agent
Ingest section with the security model and agent wiring via
PULSE_AGENT_CONNECT_URL) and an API Security cross-reference in
AGENT_SECURITY.md. Also aligns WEBHOOKS.md multi-tenant licensing wording with
MULTI_TENANT.md / API.md (Enterprise license + multi_tenant capability).
Rename the Pro-gated "Alert-Triggered Analysis" toggle to "Container
Update Risk" with container-update-scoped copy. The enterprise
AlertTriggeredAnalyzer only assesses docker-container-update alerts and
returns nil for all other types, so the old general name was misleading
and collided with the genuinely general "Alert-Triggered Patrols"
control. Reorder the panel so the explanatory box leads, followed by
Alert-Triggered Patrols plus its severity selector, Anomaly-Triggered
Patrols, Container Update Risk, then Autonomous critical remediation.
The rename is presentation-only: persisted settings keys
(alert_triggered_analysis, patrol_alert_triggers_enabled,
patrol_alert_trigger_min_severity) are unchanged.
Adds a "Multi-tenant / MSP and PSA integration" section to WEBHOOKS.md
covering per-org isolation, org resolution order, and the two PSA
integration models (push via per-org Generic webhook, pull via
org-scoped read API). Documents existing capability; no code change.
Operators can set PULSE_AGENT_INGEST_PORT to serve agent report and
management traffic (/api/agents/*) on a second listener, so that surface
can be placed on its own network or firewall boundary without exposing
the web UI or the rest of the REST API on that port. The dedicated port
serves only the /api/agents/* prefix and 404s everything else.
The option is additive and fail-closed: it is disabled at 0, the main
listener keeps serving agent ingest so existing single-port deployments
and agents are unaffected, and validation rejects out-of-range ports or
collisions with the frontend or HTTP redirect ports.
Previously an alert that triggered Patrol ran a broad health check that
explicitly ignored the threshold breach. Now an alert carries its real
payload (metric type, value, threshold, identifier, level, message) into
the patrol scope, and the alert_fired run is framed around root-causing
that specific breach instead of a general assessment.
Three coordinated changes:
- Carry the alert payload into PatrolScope.AlertContext through the alert
bridge (PatrolTriggerEvent), so the patrol prompt sees the breach
specifics rather than just an alert-type string.
- Frame alert_fired patrol runs around the breach: replace the
"ignore threshold breaches" instruction with a root-cause directive
targeting the alert's metric and threshold.
- Add per-rule control via AIConfig.AlertTriggersInvestigation: a master
enable, a minimum-severity floor (patrol_alert_trigger_min_severity,
default critical-only), and an optional alert-type allowlist
(patrol_alert_trigger_types). The router's bridge callback consults the
policy and drops non-qualifying alert_fired events before queuing a
scoped patrol. A config-panel selector persists the severity floor.
Adds config, handler, and frontend proof tests, and updates the affected
subsystem contracts.
Adds public self-serve signup for the hosted MSP offering alongside the
existing individual Cloud signup page. New handlers serve /cloud/msp/signup,
/cloud/msp/signup/complete, and /api/public/msp/signup, gated behind the same
PublicCloudSignupEnabled flag. Per-tier Stripe prices
(CP_MSP_STARTER_PRICE_ID, CP_MSP_GROWTH_PRICE_ID, CP_MSP_SCALE_PRICE_ID) are
validated against the canonical msp_starter/growth/scale plan versions; the
page renders an explicit "not open yet" notice when no MSP price is
configured. Checkout sessions carry account_kind=msp metadata so the
provisioner seeds an isolated operator workspace. The cloud and MSP JSON
signup endpoints now share one checkout skeleton parameterized per path.
Refs #1474
Separate TrueNAS native disk state from SMART health so null or unavailable smart_status projects as UNKNOWN without replacement risk, while explicit SMART failure and native failure states still alert.
Proof:
- go test ./internal/truenas ./internal/unifiedresources ./internal/storagehealth
- npm --prefix frontend-modern test -- src/features/storageBackups/__tests__/diskPresentation.test.ts
The Cmd/Ctrl+K command palette and the / global search shortcut are
the navigation affordances worth calling out. The g-chord shortcuts
are not a notable customer-facing feature for the rc.6 prerelease
notes; the keyboard shortcut modal already documents them in-product
for anyone who wants to discover them.
The internal rename chain in rc.1-rc.5 went Agents -> Standalone ->
Machines but neither Agents nor Standalone shipped as user-visible
labels for the Pulse Agent inventory page. Users coming from v5 know
the page as Hosts. The rc.6 release docs incorrectly framed the
change as 'Standalone surface renamed to Machines' which references
an internal name no public release ever exposed.
Reframe in the shipped v6 docs and the rc.6 packet drafts as
'Hosts page renamed to Machines'. Top-level page listings updated
to use Machines (not Standalone). Keyboard shortcut listing
updated to 'g s Machines' (not Standalone). 'Standalone-to-Machines
surface evolution' rewritten as 'Hosts-to-Machines rename'.
Touches:
- docs/releases/RELEASE_NOTES_v6.md
- docs/releases/V6_CHANGELOG.md
- docs/releases/V6_RC_OPERATOR_SUPPORT_PACK.md
- docs/releases/RELEASE_NOTES_v6_RC6_DRAFT.md
- docs/releases/V6_CHANGELOG_RC6_DRAFT.md
- docs/releases/V6_RC6_OPERATOR_SUPPORT_PACK_DRAFT.md
The internal route path /standalone is unchanged because that is a
code-level identifier, not a user-facing label.
Stages the doc-only subset of rc.6 packet prep work on top of
df7934936. Holds VERSION bump and the registry/contract governance
reconciliation back for a focused later pass since the IA revert
created broader subsystem-contract debt than the packet prep can
absorb.
- docs/releases/V6_RC6_OPERATOR_SUPPORT_PACK_DRAFT.md: new 300-line
operator support brief mirroring the rc.5 pack with rc.6 themes:
pre-release for testing framing, platform-shaped frontend revert
explanation, vSphere as a first-class platform, Machines surface,
TrueNAS native detail UX, FilterBar adoption, Patrol capacity-
forecast and PDM bridge, free-first self-hosted posture, install.sh
smoke gate. Carries the rc.5 free-first paid-continuity wording
through unchanged so the operator-support-pack policy test holds.
- docs/releases/RELEASE_NOTES_v6_RC6_DRAFT.md,
docs/releases/V6_CHANGELOG_RC6_DRAFT.md: fill in the SHA audit
numbers (v6.0.0-rc.5..df7934936, 616 commits, 1379 files / 139185
insertions / 67870 deletions). Add the licensing-continuity
paragraph carrying the rc.5 Pulse Mobile pairing for handoff
copy through unchanged so the discovered-packet policy test holds.
- docs/RELEASE_NOTES.md: link the rc.6 draft packet as current, push
rc.5 to historical.
- docs/UPGRADE_v6.md: round out the prerelease packet pointer block
with the rc.6 operator support pack path alongside the release
notes and changelog already pointed at in df7934936.
- docs/releases/V6_PRERELEASE_RUNBOOK.md: add 6.0.0-rc.6 to the
version-examples list and bump the RC_VERSION export shown in the
RC release steps to rc.6.
Out of scope for this commit and intentionally held back until
governance reconciliation can land coherently:
- VERSION bump to 6.0.0-rc.6 (triggers deployment-installability
shape-guard which cascades into registry/contract audits against
72 dead-file references left over from the rc.6 IA revert).
- docs/release-control/v6/internal/status.json,
docs/release-control/v6/internal/subsystems/registry.json, and the
10 subsystem contract .md files that need post-IA-revert cleanup.
- scripts/release_control test fixture refreshes.
- The rc-to-ga-promotion-readiness-blocked record regen (gated on
VERSION bump landing).
Working copies of the held-back files are preserved at
/tmp/rc6-prep-backup-2026-05-27/ with a MANIFEST.md describing each.
rc.1-rc.5 shipped a unified /infrastructure /workloads /storage
/recovery top-level layout; rc.6 reverts the frontend to platform-
shaped pages (Proxmox / Docker / Kubernetes / TrueNAS / vSphere /
Standalone) on the same unified resource backend.
Updates the shipped v6 release docs to match:
- RELEASE_NOTES_v6.md and V6_CHANGELOG.md rewritten to describe the
v6 layout as platform-shaped on a unified backend, with a paragraph
in each explaining the rc.6 revert and the operator feedback that
drove it.
- UPGRADE_v6.md prerelease packet pointer bumped from rc.5 to rc.6.
- MIGRATION_UNIFIED_NAV.md gets a top-of-file revert banner that
redirects bookmarks targeting the unified routes to their platform-
shaped equivalents; the original content is preserved below as a
Historical Context section so the 19 tracked references into that
doc still resolve.
Also adds the rc.6 draft packet:
- docs/releases/RELEASE_NOTES_v6_RC6_DRAFT.md
- docs/releases/V6_CHANGELOG_RC6_DRAFT.md
Validation SHAs in both drafts are left as <populate at packet
finalisation> markers; they fill in when the release-control packet
runs.