## Problem The safe-profile migration's authority reduction (`POST /api/agents/collector/reduce-authority`) needs the agent's ID on its token: `bound_agent_id` must be set and equal the ID the installer sends. Until now only a command-channel registration wrote that ID. Deploy enrollment (since rcourtman/Pulse#2542), Proxmox auto-registration and pre-v6.1.1 installs all leave a token bound to the node's hostname with no agent ID. The ID is filled in only at the agent's first command registration. An agent whose token lacks `agent:exec`, or that never connects the command channel, never gets an ID on its token. The reduction then refuses it with "Collector credential binding mismatch" for as long as the agent runs. Before #2542, deploy tokens instead carried an invented `agent-<hostname>`, which failed the same check. ## Fix The report path now records the agent's identity on such a token, under the same rules as command binding: - **When it binds.** After `/api/agents/agent/report` resolves a report, a token with a `bound_hostname` and no `bound_agent_id` records the server-resolved host ID. This happens only when the report presented that same ID. In the same run that is the ID the agent's command registration presents. It is also the `agentId` the response acknowledges, which the agent persists and the installer sends to the reduction. - **Same decision as the command channel.** `agentbinding.EvaluateReportedIdentity` records only where `Evaluate` would first-bind, legacy-migrate or backfill that ID for the reported hostname. It also requires: - a token bound to at most one organization, whose command-session organization matches the report's; - a collector, legacy full-trust or empty runtime role; - a token that has not expired. - **Only a host Pulse actually holds.** The resolved host must be one the monitor holds under that token (`host.TokenID`). Mock mode's discarded, unresolved acknowledgements therefore never qualify, however mock mode is toggled. - **Judged on the report's own hostname.** That is the hostname the agent's command registration presents, and it matters because a report rejected by the ordering watermark is answered with the stored host. - **Nothing else changes.** The path writes `bound_agent_id`, `bound_at` and the binding version. The binding version makes the ID immutable to every later registration. Scopes, runtime role, command policy and `bound_hostname` are never touched. A bound ID never moves and a deploy placeholder is never repaired. A recorded identity grants no execution authority: command admission still needs `agent:exec` and the same ID. - **Same store and lock as command admission.** The write goes to the router's canonical token store and persistence, under the token lock command admission holds, against the live record. A failed save restores the previous metadata and the next report retries. - **Cheap screen first.** The handler checks the request's token snapshot before taking the exclusive token lock, so already-bound or ineligible tokens cost nothing per report. Contracts updated: agent-lifecycle, security-privacy, api-contracts and storage-recovery. ## Behaviour change: the first recorded identity is final As with a command registration, the first identity recorded is final. Take an agent that reported under its machine ID while it had no command channel, for example with commands off. If it is later restarted under a different `--agent-id`, its command registration under the new ID is refused on that token. A report replayed from an earlier run's buffer has the same effect. This is exactly what happens today when that first run's command channel did connect. `TestReportedIdentityIsFinalForALaterReconfiguredAgent` pins the behaviour. Command-channel identity convergence lifts it: that follow-up to #2542 has the agent register under the identity Pulse names in the report ack, and it builds on this decision. ## Tests - **`TestContractDeployEnrolledCollectorReducesItsAuthorityAfterItsFirstReport`** (`internal/api/contract_test.go`) replays enrollment without commands, the first report and the reduction, through the real router. It checks that the token's scopes and runtime role are unchanged and that command admission is still refused. It fails without the fix: the reduction returns 403. - **Boundary tests in `internal/api/security_regression_test.go`:** - `TestReportedIdentityMatchesCommandRegistrationAndNeverMoves`: the recorded ID matches the agent's own command registration; neither a second identity's registration nor another machine's reports moves it; an ID the command channel bound first survives a report that resolves elsewhere. - `TestReportedIdentityRecordsNothingWhenTheResolvedIdentityIsNotThePresentedOne`: an agent with a configured `--agent-id` records nothing, and its command registration is still admitted. - `TestReportedIdentityIsFinalForALaterReconfiguredAgent`: pins the trade-off above. - `TestReportedIdentityJudgesAReplayedReportByItsOwnHostname`: a report the ordering watermark rejects is answered with the stored host, and is judged on its own hostname rather than the stored one. - `TestReportedIdentityRestoresTheTokenWhenTheSaveFails`, `TestReportedIdentityRequiresTheTokensOwnOrganization` and `TestReportedIdentityIgnoresAnExpiredToken`. - `TestReportHandlerOffersOnlyQualifyingLiveReportsToTheRecorder`: the handler's snapshot screen and mock mode. - **`TestApplyHostReportStampsTheTokenOnlyOnHostsItHolds`** (`internal/monitoring/monitor_host_agents_test.go`) pins the monitor properties the handler relies on: - an applied report carries its token's ID; - a watermark-rejected report returns the stored host and hostname; - mock mode's discard acknowledgement carries no token. - **`TestEvaluateReportedIdentityRecordsOnlyWhatCommandRegistrationWouldBind`** (`internal/api/agentbinding/policy_test.go`) is the policy table: the deploy, auto-registered, legacy and equivalent-hostname cases, plus every refusal, including a versionless token already bound to an ID. - **Mutation checks.** Each of these fails at least one named test: - dropping the wiring; - allowing presented ≠ resolved; - letting a bound ID move; - allowing multiple organizations; - allowing any runtime role; - skipping `Evaluate`; - not stamping the binding version; - not restoring on a failed save; - dropping the organization or expiry check; - dropping the held-host check; - judging by the stored host's hostname; - dropping the snapshot screen. Reviewed adversarially in three passes: 1. The first pass found two issues: the overstated "same ID as command registration" claim, now scoped to the same run and paired with the final-identity trade-off; and a mock-mode toggle race, replaced by the held-host check. 2. The second pass found that replayed reports were judged on the stored hostname, now judged on the report's own. 3. The third pass found nothing further. Governance: - `canonical_completion_guard.py` passes in CI mode on this commit. - `registry_audit.py --check`, `contract_audit.py --check` and `status_audit.py --check` pass. - All governance `*_test.py` suites from `canonical-governance.yml` and `go test ./internal/repoctl` pass. ## Not in this change - **Presented ≠ resolved.** When the agent presents an ID other than the one Pulse resolves (a configured agent ID, a continuity fork), nothing is recorded. Reconciling that disagreement belongs to the command-channel identity convergence follow-up. - **Tokens bound to no hostname.** Generic host install tokens without `agent:exec` still get their binding only from command-channel first use, so their reduction still has no ID to match. - **Historical deploy placeholders.** Tokens still bound to `agent-<hostname>` are repaired only by command registration, so a placeholder token without `agent:exec` still cannot reduce. - **Builds on #2542**, which has merged; this branch adds one commit on top of it.
Pulse documentation
Start here for installation, platform setup, security, operations, and Pulse Intelligence. Commands, configuration keys, image names, API fields, and product identifiers remain untranslated in localized guides.
Start here
- Install Pulse — signed Proxmox/Linux installation, Docker, Docker Compose, Kubernetes, and first-run setup.
- Production deployment and security — least-privilege Proxmox setup, root-agent boundaries, discovery, supply-chain verification, plan limits, scale evidence, and a rollout checklist.
- Upgrade from Pulse v5 — migration prerequisites, rollback, agent continuity, and post-upgrade checks.
- Releases and update channels: choosing Stable or Preview, beta and RC expectations, and how releases are tested and promoted.
- Configure Pulse — authentication, notifications, discovery, retention, and system settings.
- Deployment models — data locations, lifecycle, and differences between supported deployment paths.
- Troubleshooting and FAQ — common failures, diagnostics, and operator questions.
Localized getting started guides: Deutsch · Español
Platforms and agents
- Proxmox Backup Server
- Proxmox Mail Gateway
- Docker and Podman
- Kubernetes and Helm
- TrueNAS SCALE and CORE
- Unified Agent
- Agent security
- VM disk monitoring
- ZFS monitoring
- Temperature monitoring
VMware vSphere support is early access. Current builds expose dedicated vSphere inventory and recovery context, but operators should validate the integration against their own vCenter before production use.
Monitoring and operations
- Metrics history
- Recovery data
- Webhooks
- Automatic updates
- Centralized agent management (Pro)
- Operational trust model
- Current product screenshots
Pulse Intelligence
- Assistant, Patrol, and external-agent overview
- Patrol modes and safety
- Assistant safety model
- External agent HTTP and MCP substrate
Patrol watch-only analysis is available on Community with a local model or the operator's own provider. Investigation and governed fixes require the relevant Pulse Pro capabilities.
Security, privacy, and access
- Production deployment and security
- Security review scope
- Authentication and credential storage review packet
- Security guide
- Privacy and telemetry disclosure
- OIDC and SSO
- Proxy authentication
- Role-based access control (Pro)
- Audit logging (Pro)
- Reverse proxy configuration
- Code-signing policy
Plans and managed access
- Community, Relay, and Pro capabilities
- Relay and Pulse Mobile handoff
- Multi-tenant organizations (Enterprise/custom)
- Provider-hosted MSP operations (request-assisted)
Pulse Cloud is not generally available. Ordinary self-hosted Pulse remains the primary installation path; MSP and Enterprise access are explicit commercial paths rather than defaults in self-hosted setup.
Development and reference
Detailed design notes and dated migration specifications may remain in this directory for maintainers, but they are not operator setup guides unless they are linked from the sections above.
Previous versions and migrations
- Upgrade from v4 to v5
- Retired unified-navigation migration — historical context only; current Pulse uses platform-shaped navigation.
- Move a Pulse installation
Found a bug? Use the issue forms. For setup questions, use GitHub Discussions.