The v6.0.5 host-adoption fix (f85009913) only applies when a
re-registering agent matches a top-level instance host, so an agent on a
non-primary cluster member never benefited: its registration created a
standalone instance that ConsolidatePVEInstances folded back into the
cluster, and the fill-empty endpoint merge silently discarded the agent's
fresh address. The member row kept showing the corosync short-DNS host
rebuilt on every re-discovery (the "Install issues with V6" support
thread; a reinstall on v6.0.5 still showed the stale name).
Canonical auto-register now matches cluster member endpoints directly:
address identity against the agent's candidate list first, then an
unambiguous corosync node-name match. The Pulse-verified selected host is
adopted as the member's IPOverride, the durable field re-discovery
preserves and polling prefers, plus the fingerprint captured from that
address. An admin-managed override absent from the candidate list is
preserved, mirroring shouldPreserveExistingAutoRegisterHost. Credential
writes stay restricted to a same-token-identity secret refresh (reinstall
rotates the agent's token in place, so the stored secret is already
invalid) and full promotion onto a credential-less cluster; a member's
distinct per-node token never replaces working cluster credentials, and
no standalone instance is created for consolidation to discard.
A follow-up sent during an active run now offers itself to the running
agentic loop via POST /api/ai/sessions/{id}/steer. Accepted steers join
the loop at its next turn boundary (the abort-check site) as plain user
messages, are announced with a steer_applied stream event so the drawer
settles the pending row, and persist through the end-of-run save. A
steer carries prompt text only: no route, control-level, or autonomy
changes, no turn-budget extension, system sessions rejected, and the
per-session inbox is bounded (steer_backlog overflow). Delivery is not
guaranteed by acceptance: a run that ends first discards the inbox and
the row drains as an ordinary queued turn, so pre-steering queue
semantics remain the fallback. Steering rows lose edit/remove once
accepted.
Chat turns, compaction, and title calls now stamp session_id on their
cost.UsageEvent, and the done event carries session_cost_usd summed from
the operator ledger (cost.Store.SessionCostUSD). The drawer's last-turn
summary appends '$0.12 session' with a sub-cent floor. The figure is
omitted whenever any of the session's models has unknown pricing, and
free local models price known-at-zero, so no figure is ever partial.
Retrying a failed turn re-sent the prompt without removing the persisted
turn, so session history double-recorded the prompt. Session undo now
accepts an expected-prompt guard (a stale retry can never remove a
different turn); retry drops the replaced turn server-side before
re-sending. The latest settled assistant answer gains a hover-revealed
Regenerate button that reuses the same path.
Sibling of #1559: canonicalIDFromIdentity hashed the short hostname in
its cluster arm, so two machine-keyless Docker Swarm members with FQDN
hostnames sharing a first label (cloud.a, cloud.b) derived the same
cluster:<swarm>:cloud canonical ID and fully merged in the registry.
PVE is unaffected (single-label node names hash identically).
Both hostname-derived arms now hash NormalizeFullHostname. Derivation
stays a pure function of identity so store-less registries keep deriving
the same IDs as the durable one. Compatibility for hosts minted under
the short era: journal reads already merge both eras via
ResourceIdentityPin.EraIDs, and new canonical-ID succession re-keys
resource_operator_state and action_audits rows to the successor ID at
pin persist (never across a contradicting machine key, never while the
old ID is still live, never rewriting journal rows) so operator intent
like never-auto-remediate survives the era change. Already-merged
keyless pairs cannot be retroactively split; the merged rows succeed to
whichever member persists first.
The code fix (preserve full dotted hostnames in identity pins and the
presentation host coalescer, with short/FQDN equivalence kept for
matching) rode commit 1d3b8e194 through a shared-index race. This
commit carries the unified-resources subsystem contract update and
moves the end-to-end regression test into registry_test.go.
Every email covering a single alert now carries In-Reply-To and
References headers set to a deterministic incident thread ID derived
from the alert ID and firing start time, so mail clients thread the
firing, re-notification, and resolved emails of one incident together.
Message-ID stays unique per send because re-notified incidents send
multiple emails and some providers de-duplicate on Message-ID. Grouped
emails skip threading since firing and resolved batches rarely contain
the same alert set.
Addresses #1543 (discussion)
The installer's unit hardens with NoNewPrivileges=true, which strips the
ping binary's setuid/file capabilities, so ICMP availability probes could
never work on a systemd install. Grant the capability ambiently instead,
document the systemctl edit override for existing units, and pin the
hardening block with an install test and contract clause.
Addresses #1554 (discussion)
Patrol detection reuses the durable patrol-main session (and investigations
use investigation-* sessions) as forensic logs. Those surfaced in the
Assistant empty-state Recent sessions list as resumable chats, titled with
the raw triage seed ("# Deterministic Triage Results Scanned ..."), which
reads as a broken user session (reported by a 6.0.5 user via support).
Session summaries now carry a server-derived system flag for Pulse-owned
background runs and the quick-resume list filters them out. The drawer
session picker and the Settings sessions panel still list them for
inspection.
Discovery is high-fan-out background work (one model call per container or
service), but GetDiscoveryModel fell back straight to the shared default
model. An operator who picked a cheap Patrol model to keep background AI
inexpensive still had scheduled service-context refreshes burning the
expensive shared model, dozens of calls per run, with no chat activity
(reported by a 6.0.5 user via support). Discovery now follows the same
fallback chain as auto-fix: DiscoveryModel, then PatrolModel, then the
shared default. Settings copy and the ai-runtime contract state the chain.
The three sorting commits (20cccc1a9, f66c4b46b, cf0f027b6) added the
shared sortable-header fabric and adopted it across Docker, Kubernetes,
TrueNAS, vSphere, and Proxmox nodes tables, but landed frontend-only,
which skips the local governance audit; Canonical Governance CI then
flagged frontend-primitives and unified-resources for missing contract
deltas. Record the fabric's interaction contract (shared sort state,
persistence, missing-values-sink, aria-sort, canonical alignment) and
the two-layer ordering rule (status-first default, user sort on top).
OrchestratorChatService is replaced, not extended: it exposes only
ExecuteInvestigationStream (returning a structured
OrchestratorInvestigationResult) and ListInvestigationTools. The generic
ExecuteStream, SetAutonomousMode, ListAvailableTools, and the
AutonomousMode request field are gone, along with
OrchestratorCommandExecutor, OrchestratorApprovalStore, and the
autonomy/fix-verifier/license dependency interfaces. OrchestratorDeps
now carries a REQUIRED ActionBroker and no command/autonomy deps.
The pulse investigation adapter is rewritten to drive
ExecuteInvestigationStream, injecting proposal/finding/investigation
identity from trusted context and feeding a per-org proposal catalog
resolved from the tenant-bound action lifecycle (so acceptance and
planning validate identically). The retired command-execution,
autonomy, and command-shaped approval adapters are deleted; the router
wires the broker and catalog factories onto the AI settings handler.
The sixth side door is removed: PatrolService.generateRemediation-
PlanFromInvestigation and its call, which copied Fix.Commands into an
executable enterprise remediation plan, are deleted, pinned closed by a
source-audit test.
ActionCapabilityParamInfo gains Pattern (mapped by the broker), so the
canonical planner's pattern validation survives the cross-repo
boundary.
L20 readiness assertion RA35 asserts every Patrol-initiated
infrastructure mutation originates as a typed proposal and reaches
execution only through the canonical action lifecycle, with unsupported
proposals failing closed. Contract prose updated across ai-runtime,
api-contracts, agent-lifecycle, performance-and-scalability,
security-privacy, and storage-recovery.
Lands together with the pulse-enterprise orchestrator migration (the
replace directive means both heads move as one window).