## Problem A rebuild applies canonical-ID successions to the store, which moves the predecessor's operator row (or carries its remediation lock onto an existing successor row), before `replaceRegistryLocked` publishes the generation that lists the successor. For the length of the rebuild the previous generation keeps serving the old ID, and the three places that decide on the lock read the row of that ID: - `actionlifecycle.validateExecutionPolicy` (`ValidatePlanFresh` passes because `registry.Get(oldID)` still finds the resource, then the lock read finds no row and `!found` means not locked); - the Patrol broker's `loadPatrolActionPolicyInputs` (via `Service.ResourceOperatorState`), which records `resource missing` and authorizes against an empty state; - `isResourceRemediationLocked` in `internal/ai/tools/action_audit.go`. Nothing re-resolves the ID through the new generation, and the plan step reads the old ID's row too. Every kind of succession reaches the window: pin-driven (`PersistIdentityPins`), Proxmox guest eras, and the host and Docker reporter eras added in #2800. I did not reproduce a concurrent dispatch; I reproduced the state it would read: `TestRemediationLockHoldsOnTheRetiredIDWhileThePreviousGenerationServes` hooks the store between the succession and the publish, and fails on `main` in all 8 variants (2 era kinds x 2 stores x 2 lock kinds) after its fixture checks confirm the previous generation still lists the old ID. ## Fix `GetResourceOperatorState` (both stores) follows the recorded successions (`canonical_id_successions`) when the requested ID holds no row. One walker, `followRetiredID`, serves both stores: - it answers as the ID the chain **ends on** would, with that row's remediation block alone (`CarryRemediationLock` into an empty survivor, `system:identity-change`, dated by the source row so a plan's policy revision is stable across reads); - an ID with a row of its own answers with it; a lock the operator lifted on the successor (edit or clear) stays lifted, and a stale unlocked row left on an earlier ID does not hide a lock at the end; - a chain that returns to an ID it has seen has no end, so a lock anywhere on it counts; there is no depth cap; - nothing is written, so no ghost rows (`ListResourceOperatorStates` still lists only rows); - the SQLite walk runs in one transaction. With one statement per hop, a reverse succession committing mid-walk can move the lock onto an ID the walk already passed (a review finding). `TestRetiredCanonicalIDReadSeesOneStateWhileTheLockMoves` fails when the transaction is reverted. Delaying the move was rejected: later steps of the same rebuild read the store, and it would not protect plans already issued against the old ID. The alternative of leaving a lock-only row on the predecessor conflicts with `TestSuccessionBatchOrderKeepsLinkedRemediationLock`'s "no row for a re-keyed intermediate" and leaves ghost rows. Seven existing assertions used `GetResourceOperatorState(oldID) == not found` to prove "the row moved"; they now read rows through `ListResourceOperatorStates` (`operatorStateRow`), with the same intent. ## Limits (in the contract) Only the block crosses, so the retired ID still reads none of the row's other settings while the window is open (as before). An operator edit made to the retired ID inside the window goes to that ID's own row, not the successor's (the API writes the ID the served generation lists, and a succession applies once per predecessor): a lock set there does not reach the successor, a lock lifted there leaves the successor's lock in place. A retired ID that a different resource later re-mints with no row of its own reads the successor's lock until the operator writes it a row. The contract paragraph that listed the window as a limit now documents the read. ## Verification - New tests: `canonical_id_succession_test.go` (`TestRetiredCanonicalIDReads*`, 5 tests incl. a 150-hop chain, loops, chain-end, and the interleaving stress test), `registry_test.go` (the window test above), `internal/actionlifecycle` (`Execute` refuses a locked resource whose row moved, and the policy read the broker uses finds it), `internal/ai/tools` (the dispatch gate). - 9 negative controls on pulse-dev, each caught by a named test: skip the read in each store, drop the loop guard, nearest row instead of chain end, whole row instead of the block, wall-clock stamp, ignore locks on a loop, succession answer overriding an own row, and the non-snapshot SQLite walk. - `go test` over `unifiedresources`, `actionlifecycle`, `ai/...`, `api/...`, `monitoring/...`, `maintenancesentinel`, `truenas`, `alerts/...`, `agentcontext` on pulse-dev; the interleaving test 8/8 repeats and the retired-ID tests under `-race`. The only two failures are artifacts of the remote run, not of this change: `TestServerInfoEndpointReportsDevelopment` needs the `.git` the rsynced tree lacks, and `TestContainerBootstrapFailuresStopAndClean/hangup` fails 3/3 the same way on a pristine `origin/main` tree on pulse-dev (a detached `nohup` run ignores SIGHUP). - Two independent read-only review rounds before pushing: round 1 found the SQLite interleaving and two contract overclaims, all fixed; round 2 found nothing.
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.