Start the provider services after licensed compose validation so the printed bootstrap and portal link have a live control plane. Correct the first-run prompt to name only operator-provided values and retain an install contract regression test.
Change-source: pulse-maintainer
A billing-cycle change can retain the licence ID and plan while moving the signed paid period earlier. Compare the full signed expiry, replace the old token and restart, with regression coverage and the matching cloud-paid contract guarantee.
Change-source: pulse-maintainer
A sandbox run of the purchase path showed that a provider who changed
plan in the Stripe billing portal came back to the portal's Clients page
still on the old plan. It stayed that way until the next six-hourly
refresh, unless they found the "Just paid? Apply my purchase now" button,
whose wording does not fit a plan change. Manage billing now returns to
?provider_msp_checkout=billing, and the Plan panel checks the licence
twice on the way back, which is enough for the plan-change webhook to
land without polling on every visit. In the same run Solo to Starter and
back applied about ten seconds after returning.
The paid panel also dropped "Licence valid to <date>". That date is the
paid period end plus 14 days of grace, so a buyer read it as the date they
had paid until. It now appears only once the licence is inside that grace,
as a warning that the subscription has not renewed.
The portal had no billing surface in provider-hosted mode, so a provider
on the free evaluation could not see when it expired, how many client
slots it had left, or any way to pay. Provider-hosted mode now shows the
billing section as Plan. It lists the evaluation or paid plan, its
expiry and the workspaces in use, and on an evaluation it offers the
plans above the current cap at the licence server's Stripe prices.
Owners and admins get Buy, Manage billing, and "Apply my purchase now";
read-only members are told who can buy. After Stripe returns to the
portal the panel keeps refreshing the licence until the purchase lands,
then polls until the restarted control plane serves the new plan,
without reloading the page.
Exercised on a fresh v6.4.1 provider bundle running this build against
a stand-in licence server: the tab, the evaluation expiry and slot
usage, monthly and annual pricing, Buy reaching checkout with the
platform's licence ID and portal return URL, the periodic refresh
signed with the lab's lease key, and a 390px layout with no horizontal
overflow.
A provider could stand the MSP bundle up and evaluate it without asking
anyone, but paying still meant emailing its public key to Pulse and
copying a licence file onto the host by hand. With the licence server's
self-serve purchase path (pulse-pro license-server/provider_msp_purchase),
the control plane can do the whole thing itself.
The control plane now signs a challenge with its lease signing key to
fetch the licence its subscription entitles, validates it exactly as
startup does, requires it to bind this platform's key, and keeps it in
the data directory, because the host licence is a read-only secret
mount. Startup prefers that renewed licence while it validates, so a
paying provider keeps starting after the self-issued evaluation on the
host expires. A changed licence restarts the control plane through the
existing graceful shutdown, since the plan version is read at load by
every workspace-limit, backup and status path; client workspaces keep
running. Preflight, proof, recovery and backup accept either signed
source.
The portal gains owner and admin routes for plan state, checkout, the
Stripe billing portal and an immediate licence refresh, and the plan list
comes from the licence server so it shows only what can be bought, at
Stripe's price. Adds the msp_solo plan: 3 client workspaces, the first
paid step above the 2-workspace evaluation.
provider-msp status, which upgrade.sh and the install proof gate on,
runs in a one-off `docker compose run` container attached only to the
provider ingress network. Its health check dialled each client's
container on the client's isolated network, which that container cannot
reach, and an unreachable client counts as unhealthy. So on any install
with a real client, status failed with "unhealthy active workspaces" and
./upgrade.sh --dry-run exited 1: the backup-gated upgrade path refused to
run as soon as a provider onboarded anyone. CI proofs never saw it
because they only ever run against an empty install. Reproduced on a
fresh v6.4.1 provider bundle with one client whose container Docker
reported healthy.
The runtime image declares a HEALTHCHECK that probes the same
/api/health inside the container, and Docker records the result where
any caller with the socket can read it. Use that when it exists and keep
the HTTP probe for images without one. With the change the same install
reports healthy_tenants=1 and upgrade.sh --dry-run plans cleanly.
The existing regression only rejected legacy backup-age key prefixes, so it could pass while a canonical orphaned alert remained active. Assert a real orphan alert exists first, then disable the option and require every backup-age alert to clear. This guards the opt-out relevant to #1741 without changing runtime policy.
Change-source: pulse-maintainer
Extend the existing same-hostname host-agent and TrueNAS fixture to the disk-write series reported in #1966. Assert connection-scoped agent targets, distinct source values, and that TrueNAS pool capacity writes are not disk-write samples. Keep cross-target equality explicitly synthetic pending reporter evidence.
Change-source: pulse-maintainer
Every provider MSP client workspace runs in hosted mode, where users sign
in through the control plane handoff. The install endpoints decided
whether to mint a token from the workspace's own config, which on a fresh
client has no local credential, API token, proxy secret or SSO provider.
So Settings > Infrastructure > Generate token answered 200 with an empty
token and the Proxmox flow built a command without one: a provider could
not install a single agent from the client workspace. Found on a fresh
v6.4.1 provider bundle.
The router's configTransferAuthenticationConfigured already counts hosted
mode as enforced authentication; the agent-lifecycle copy of the rule
never gained that term. Add it. Self-hosted runtimes with no auth keep
the token-less command.
A provider opening a client workspace from the Pulse Account portal
arrives in that client's organization as its owner. ensureAdminSession
admits an org manager to the org-bound settings routes, but the security
status snapshot reported every settings capability as false for any
org-scoped session. Since the Settings navigation started hiding gated
panels in August, the owner saw only General: no Infrastructure page to
connect the client's Proxmox or install agents, and no Reporting page
for the client reports. The portal's handoff to
/settings/infrastructure fell through to General. Found on a fresh
v6.4.1 provider bundle, where /api/config/nodes answered 200 for the
same session that /api/security/status said could reach nothing.
Route and snapshot now share one settings-admin rule,
sessionUserHasSettingsAdminPrivileges. The snapshot keeps it out of
sessionIsAdmin: it widens only infrastructureRead, availabilityRead and
reportingRead, whose handlers read the organization's own config and
monitor state. Instance administration, detailLevel, authUsername and
billingAdmin stay withheld from org-scoped sessions, as the scoped
local session contract requires.
Every stable cut re-pins CANONICAL_DEFAULT_PULSE_VERSION in
scripts/install-docker.sh from the RC to the stable version, and the
stable install-metadata test requires it. The drift allowlist omitted
that file, so no release candidate could be promoted without
hotfix_exception. The rule arrived on 2026-09-01, after v6.4.1, so no
stable promotion has run under it yet. v6.4.5 would be the first to fail.
The installer is shipped content, so the resolver accepts it only when
its diff against the candidate changes nothing but that pin line. Any
other installer change still counts as drift.
Go _test.go files are never compiled into a shipped artifact. A stable
cut can need test-only corrections, as v6.4.5 needs for the RC-derived
install-metadata contract, so they no longer count as unsoaked content.
Tests cover the allowed shapes, the refused near misses, and the pin
check against a real git diff.
Exercise same-hostname host-agent and connection-scoped TrueNAS records through canonical resource resolution and unified metric routing. Keep the synthetic equality check explicit so it does not imply the reporter's live samples matched.
Change-source: pulse-maintainer
Compose the exact optimization and benchmark source onto the current assigned source so correctness and paired costs can be measured without replacing the admitted Node digest candidate.
Change-source: pulse-maintainer
Keep the canonical release and provider-MSP frontend builders on the proposed immutable amd64 Node 24 digest. Bind the same-commit change to the deployment contract, release-build metadata proof and provider-MSP rollout-control proof; hosted build and installed rollout remain separate evidence.
Change-source: pulse-maintainer
Compose the reviewed shared-lock change with the clean-read and dirty-cache comparison benchmarks so correctness and both cache states can be measured on the same candidate.
Change-source: pulse-maintainer
Separate full-cache rebuild cost after a one-resource invalidation from the clean cached-read benchmark for comparable lock-path evaluation.
Change-source: pulse-maintainer
The 2026-09-05 direction left release timing to open-ended judgment. Since
then no line has been cut from main and no stable has shipped, while every
candidate that passed qualification was withdrawn. Restore a fixed cycle
with a closed list of stop reasons, and point at the maintainer's one-page
release policy.
The clean-soak rule required the stable dogfood instance and demo server to
run the candidate, but both run the current stable release by design, so
no candidate could ever soak clean. Define the soak from open regression,
security and data-loss reports, preview telemetry and the candidate
observation guest where it is operational.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AHsrZFUgqmohpszCMpaPx
Use shared locks on clean cached reads, rebuild only when dirty, and retry after rebuilding so concurrent invalidation cannot return stale views. Add a registry-level regression test that cached reads coexist with another reader. Observable resource behavior is unchanged.
Contract-Neutral: Internal lock-acquisition optimization; cached ReadState values and resource/API behavior remain unchanged.
Change-source: pulse-maintainer
The reviewed core-runtime commit is merged exactly, and the two browser-verified frontend source files remain byte-identical to its receipt. Rebind only the receipt base to the integration merge so cumulative governance checks retain exact parent identity.
Change-source: pulse-maintainer
The PBS API reports the node hostname about itself, but host-agent correlation only consulted the configured connection label and endpoint. When a connection is configured by IP or a DNS alias the agent never reports, the host agent could not be linked to its PBS connection, so the Backups drawer lost its persisted host history target between snapshots and fell back to the PBS service key, which has no host series (#1723).
Use the reported node name in pbsInstanceCorroboratesHost and expose it on the PBS resource (pbs.nodeName). The Proxmox Backups identity correlation now includes it so the host row stays correlated when the configured endpoint is not a machine-identity token.
Rebuilt on the current canonical pulse tip e994c41c20, which already carries the web-product retention change (5d831b8765). The offline browser fixture now combines both failure modes: the connection is configured by 10.0.0.5 and linked only by the reported pbs.nodeName, and a live refresh can transiently omit the correlated host row. The check passes 12/12 across pbs-only, side-by-side and guest topologies, stable and reordered snapshots, at 1280x900 and 390x844, and never requests the PBS service target.
Contract-Neutral: Behavioral machine-identity correlation fix plus an additive optional pbs.nodeName field; no breaking contract, public API removal, or canonical-ownership change.
Change-source: pulse-maintainer
The two Profile M row-windowing cases still waited at vitest/testing-library's 1000ms default with no per-test timeout, so under the release qualification VM's load they could abort the same way Profile L did for v6.4.5-rc.3 (packet 20260923T082318Z-release). Give both the declared 15s waitFor budget on the row-cap assertions and a 30s per-test timeout, matching the adjacent Profile L and grouped/flat contracts. Assertions are unchanged.
Change-source: pulse-maintainer
The integration merges preserve the exact browser-tested frontend bytes. Rebind the content-bound receipt to the integration tip so the canonical cumulative range passes the parent-identity guard.
Change-source: pulse-maintainer