Keep the current clock and stored-History readout alongside canonical row
selection and resource-owned drawer state.
Use one Git inheritance reader for browser and canonical qualification.
Inherited modes and objects keep their parent proof, while new resolutions
and mode changes remain subject to current contract and proof requirements.
Range checks still qualify the complete proposed change.
Refs #1669
Pro sells Patrol investigation, but every install starts in Watch only
and stays there unless the operator finds the mode control. In the paid
telemetry read on 12 October, 20 of 35 Pro installs with Patrol on ran
Watch only, and none of those 20 ran an investigation in 30 days, so
the feature they paid for never ran.
An install that never saved a Patrol mode now runs Ask first when it
holds a positive investigation licence. Patrol investigates new and
re-detected warning and critical findings and proposes a fix; nothing
changes without approval. The default only applies to a positive
licence answer: a missing licence checker counts as unlicensed, so a
startup window cannot spend provider budget on a free install. Free
installs and explicit choices are unchanged.
The Patrol header says so once, with the cost (the operator's AI
provider and 30-day budget) and two actions that save Ask first or
Watch only. The notice never renders on a plan-locked install. The
header also stops claiming "Watching your infrastructure" while Patrol
cannot run yet and reads "Patrol is not running yet" instead.
Use unified resource IDs for workload rows and selection so moving a guest
between Proxmox nodes retains its open drawer and History range. Resolve
existing bookmarks through the registry instead of rebuilding client IDs.
Migrate guest metadata before accepted inventory publication and retain its
operator fields in memory if persistence fails. Native metadata and metrics
coordinates remain explicit compatibility inputs. Operator policy and paid
execution behavior remain intact.
Register the focused publication and writer proofs once. Check proof
acceptance and rejection without copying the registry catalog into tests.
Refs #1669
Reverts #2863 before any release ships it. The live Pro pricing and
Patrol pages, in English, German and Spanish, sell both modes ("applies
safe fixes in the mode you choose: Ask first, Safe auto-fix, or
Autopilot"), and paying installs chose them. Shipping the retirement
would have silently downgraded those installs while the sales page still
promised the modes.
If the modes are retired, that needs a dated notice: stop selling them
first, warn installs on those modes in-app, and then re-ship the change.
It should not arrive through an engineering cleanup.
The receipt #2863 landed stays in place; this commit adds the receipt
for the restored content.
Patrol could run some fixes on its own in two paid modes: assisted (Safe
auto-fix) and full (Autopilot, behind a server-owned acknowledgement).
Schema-v17 telemetry for the week to 2026-10-11 counts 1 assisted and 2
full installs out of 612 reporting Patrol, 4 executed Patrol actions in
30 days, all on one install, and no GitHub request for more autonomy,
while the automatic path carried its own policy leases, acknowledgement
store, per-resource allowlist, release gate and Colima lab. Investigation
is what paid installs use, so it stays.
Patrol now has two modes. Watch only reports findings. Ask first
investigates and proposes fixes, and every fix waits in Actions for an
operator to approve and run it.
- A saved assisted, full or autonomous level reads as approval, and the
update routes refuse those levels with 400 invalid_autonomy_level.
- The core Patrol action broker only plans: Plan and Submit persist the
plan and return. ExecuteUnderPolicy, the policy authorization lease,
the policy admission store methods, the Autopilot acknowledgement
persistence, routes and dialog, and the per-resource automatic-action
allowlist (store column now written as NULL) are gone.
GetPatrolFullModeUnlocked stays on the extension contract and reports
false.
- Historical action records keep their policy lease and tenant/resource
reason codes decodable, labelled as retired modes.
- The Patrol inbox no longer splits issues into "continuing without a
decision"; every current issue is an operator decision.
- Release gate RG-06 now proves Patrol never executes without an operator
decision (integration tier) instead of limited unattended autonomy,
and RG-02/RG-04 point at the surviving human-admission proofs.
- Pro plan copy, AI docs, privacy telemetry wording and the subsystem
contracts describe the two modes.
AI Discovery ran a separate model-backed service scanner beside Patrol
and the Assistant: its own store, scheduler, model route, API, tool and
drawer UI. No telemetry counter ever measured its use (the telemetry
discovery_enabled flag OR'd it with network subnet discovery), the
maintainer judged it a flop, and the context it produced is already
available to the models through pulse_query, pulse_read and the
unified-resource context sections.
This deletes internal/servicediscovery, internal/ai/infradiscovery, the
pulse_discovery tool, the /api/discovery routes, the mock Discovery
fixtures, the Discovery tabs in the resource, guest, node and Docker host
drawers, the identified-service cards, readiness badges, provenance
markers, suggested web-interface URLs, the Workloads AI Context column,
the chat "Discovery is off" hint and the AI Service Context settings page.
The old settings route redirects to Assistant settings. The model-bound
sanitizer allow-list existed only to pass Discovery context through, so it
goes too, along with the CopyableCodeRow primitive that only the
Discovery tab used.
Network subnet discovery (/api/discover, the Infrastructure discovery
settings) and the discoveryTarget agent coordinates are separate features
and stay. Stored discovery_enabled, discovery_interval_hours and
discovery_model AI settings are ignored on load; the settings API decoder
is lenient, so older frontends saving those fields still succeed.
After the useRecoveryPoints trim in #2773 the TrueNAS Protection tab
reads only the points. The decode still carried the endpoint's
pagination meta (page, limit, total, totalPages) through
normalizeRecoveryMeta, the meta field on both points response types, the
hook's initialValue and several test fixtures, none of which anything
reads.
A whole-program checker scan over src and tests (positive control: the
data member, read in the hook and a test; negative control: eight
planted readers all found) showed zero reads of the normalized or
transport meta. The one read of the transport meta was the decode
itself, and the four RecoveryResponseMeta members were read only inside
normalizeRecoveryMeta. The response is never spread, serialized or
iterated, and the query layer treats the value as opaque.
Remove the decode, the meta fields on RecoveryPointsResponse and
RecoveryPointsTransportResponse, the initialValue meta and the meta
fixtures. RecoveryResponseMeta stays: the protection postures response
type still declares it.
recoveryTransport.test.ts pins the new shape: the response types'
keys are exactly data (a type-level pin that fails under tsc), and a
backend-shaped response that includes meta decodes to the points alone.
The backend response and internal/api/recovery_handlers.go are
unchanged, and the contract prose now says the frontend decode carries
the points alone.
A before/after production build pair differs in one chunk (the TrueNAS
page chunk, 387 bytes smaller) and only by the removed statements; the
CSS and the other 93 files are identical apart from chunk hashes. The
TrueNAS Protection tab shows the same rows, filters, search results and
detail panel at desktop and phone width, and the backend still sends
meta in every response. Coverage of the hook and the decode loses
nothing but the removed meta statements.
Merge exact upstream 7f466fc183 without re-parenting its reviewed history. Retain both independent contract sections in the two documentation conflicts; no frontend resolution or runtime correction.
Change-source: pulse-maintainer
The aggregate Recovery page, deleted on 2026-05-26, was the only caller that
sent the hook's rollup, kind, mode, outcome, item type, linked resource, text,
cluster, node, namespace, workload scope, verification and time-window
options, and the only reader of its meta result. The TrueNAS Protection tab
has been the sole caller since, and passes platform, page and limit and reads
the points, the loading and error state and a refetch.
Remove the unused query options (and the subjectResourceId fold onto
itemResourceId), the meta and resolvedOnce results, and the '__all__' branch
that served a call with no query accessor; the accessor is now required. The
points endpoint and the request the tab sends are unchanged.
recoveryTransport.test.ts pins what is left: only the normalised page, limit
and platform reach /api/recovery/points even when a caller casts other
filters in, a blank platform is left off, a null query makes no request, and
the query keys, the result members and the accessor parameter are fixed at
the type level. storage-recovery.md describes the narrower hook.
The aggregate Recovery page and its rollup, facet and series hooks went on
2026-05-26, and #2728 removed the recovery query serializer. Several
recovery helpers kept exports that nothing in the shipped app reads. A
TypeScript-compiler export graph from src/index.tsx finds no reachable
importer of any of them, and git log -S dates when each lost its reader:
- recoveryPlatformModel: the rollup normalizers and getRecoveryRollupPlatforms
(last reader useRecoveryRollups.ts, 2026-05-26).
- recoveryTimelinePresentation: the tooltip-row, point-total, day-filter and
column aria-label builders (the page's timeline, 2026-05-26). The tooltip
rows were the only reader of recoveryArtifactModePresentation.ts, so that
module, its test and the audit rule that pointed page code at it go too.
- recoveryTimelineChartPresentation: the range-group class and bar
min-width helper (unread since the timeline changes of 2026-03-29).
- recoveryOutcomePresentation: RECOVERY_OUTCOMES (dashboard retirement,
2026-04-29) and the bar and text class helpers.
- recoveryDatePresentation: the date-search resolver and its normaliser,
and formatRecoveryTimeOnly (2026-05-26).
- types/recovery: the rollup, series and facet types.
The TrueNAS Protection tab also fell back to display.subjectLabel,
display.subjectType and subjectRef for labels, sorting and search. Its
points come only from useRecoveryPoints, which runs every
/api/recovery/points response through normalizeRecoveryPointsResponse.
That decode folds those fields onto display.itemLabel, display.itemType
and itemRef and drops the originals, so the fallbacks never fired. The
subject names now live on RecoveryPointTransport and
RecoveryPointDisplayTransport only, which turns any future read of them
off a normalized point into a type error instead of a silent no-op.
The points handler sends the item label only as display.subjectLabel, so
that fold is what names TrueNAS rows. recoveryTransport.test.ts now drives
backend-shaped points through useRecoveryPoints into the TrueNAS sort and
search, and pins the type split with expectTypeOf. Breaking the fold fails
the runtime test; re-adding subjectRef to RecoveryPoint fails the type check.
Tests that pinned the subject fallbacks now pin the itemLabel and itemRef
arms they stood beside. Coverage of the surviving modules is unchanged:
matched by normalised source text, no statement or branch covered before
is uncovered after.
canonical-platform-audit.mjs loses the artifact-mode rule and allowlist
entry, and the formatTimeOnly, range-group and bar min-width alternatives
of two recovery rules, since the helpers they pointed at are gone. A planted
niceAxisMax, labelEvery or tick-label tint string still trips the retained
alternatives.
The shipped JavaScript changes only by the removed code. Of 189 dist files,
93 are byte-identical and 93 differ only in chunk-hash references and SRI
strings; the TrueNAS chunk loses the datasetLabel subject terms, the
truenasPageModel chunk loses the subject search and sort terms, and the CSS
loses four rules no source uses any more (bg-gray-400 and three min-w
classes). The bundle-size check passes with no chunk renamed. Before and
after builds driven by one browser script show the same TrueNAS Protection
rows, search, filters and detail panel and the same Proxmox Backups chart,
tooltip and day selection at desktop and phone width.
registry.json, storage-recovery.md, frontend-primitives.md and
unified-resources.md drop the deleted module and describe the decode
boundary as it now stands; two storage-recovery and unified-resources
sentences that still described rollup, facet and series reads in the
frontend are corrected.
Contract-Neutral: Only residual guard demand is the platform support manifest frontend projection proof that the canonical-platform-audit.mjs path policy names (resource.test.ts, sourcePlatformOptions.test.ts, sourcePlatforms.test.ts). This commit deletes one recovery audit rule, one allowlist entry and retired alternatives in two other recovery rules, and leaves the projection sync check untouched, so editing those tests would be fabricated proof. storage-recovery, frontend-primitives and unified-resources carry substantive notes, and recoveryTransport.test.ts and frontendResourceTypeBoundaries.test.ts are the staged recovery-product-surface proofs.
Additively incorporate exact mid-turn upstream b9c98a4a16 after the bounded authenticated refresh edb1c69d663e1c28aaf2ce01a54b27bd72ade67a3d5ad505018accd2a6b21576. Retain the original reviewed tip and preceding upstream composition; publication is still gated on final-content browser and canonical evidence.
Change-source: pulse-maintainer
Additively incorporate exact upstream main 533027ee06 without rewriting accepted maintenance. Resolve the storage-recovery contract insertion by retaining both the reviewed silent-source History fence and upstream SAML/method constraints. No runtime conflict resolution is introduced; final-content evidence and exact-head checks remain required before publication.
Change-source: pulse-maintainer
The Patrol page fetched `GET /api/ai/circuit/status` on every load,
every 60 s poll, every return to the tab, and again in the background
after Retry. The store's breaker state had one production reader: the
`circuitBreakerStatus` memo in `usePatrolIntelligenceState`, which
nothing read and which #2695 removes. An open breaker already reaches
the page through `/api/ai/patrol/status` as the `circuit_open` blocked
cause, and the paused banner shows its reason.
## Change
- `stores/aiIntelligence.ts`: remove the breaker signal, getter,
`circuitBreakerStatusSignal`, `loadCircuitBreakerStatus()`, its
org-switch reset, and its `loadDashboardData()` entry.
- `features/patrol/usePatrolIntelligenceState.ts`: drop the read from
the Retry path's background supporting reads.
- `api/ai.ts`: remove `AIAPI.getCircuitBreakerStatus` and the
`CircuitBreakerStatus` type; nothing else used them. The backend route
stays.
- Tests: the client's unit tests and the store and page test mocks go.
The store bundle test still pins the exact AIAPI read set. The breaker
route stubs are removed from seven E2E specs.
- Contracts: patrol-intelligence, ai-runtime, api-contracts and
frontend-primitives say the browser reads no breaker status and how an
open breaker still reaches Patrol. Each note sits next to #2677's
correlation note.
## Evidence
- Normalized production-build compare: only the removed store loader,
signal, getter and client differ in `index.js`, plus the removed
supporting read in `AIIntelligence.js`. `check-bundle-size.mjs` passes.
- Private demo-mode stacks for the parent and this tree, driven by the
same Playwright script at 1440x900 and 390x844. Across Patrol load, tab
hide/show and stale-data Retry, the breaker read drops from 1-2 requests
per phase to none. Every other endpoint matches, the Open in Assistant
handoff is unchanged, and neither build logs a console error.
- A forced `circuit_open` Patrol status still shows "Patrol paused /
circuit breaker is open" on both builds.
- `tsc --noEmit`, eslint, and vitest for `src/stores`, `src/api`,
`src/features/patrol`, `src/components/AI`, the Patrol page test and the
boundaries test pass, as does the full pre-commit battery.
Stacked on #2695 (which stacks on #2687) with #2677 merged in, so until
those land this PR shows their commits too. The 2677 owner agreed this
read belongs in a follow-up. `git merge-tree` is clean against main,
#2677, #2690, #2691 and #2693.
## Why
Since 4edf5f8265 (2026-07-11), `ChatRequest` in
`internal/api/ai_handler.go` has no `autonomous_mode` field. Go's
decoder drops it and `HandleChat` passes approval-required mode into the
chat service for every `/api/ai/chat` request (pinned by
`TestHandleChat_CannotElevateAutonomousModePerRequest`). The browser
still put `autonomous_mode` in the request body from its send options,
and the eval runner kept a per-step override that sent it too. Both read
as if the client chose the execution mode, but the server ignored them.
## What changes
- `AIChatAPI.chat` loses the `autonomousMode` positional parameter and
no longer writes `body.autonomous_mode`.
- `SendMessageOptions.autonomousMode`,
`ChatMessageRequestContext.autonomousMode`, and the clone, retry,
restore and Explain send-option copies are removed.
- `aiChatStore.context.autonomousMode` stays as a drawer-only flag.
Scoped handoffs still show "Chat: Ask first" and "Approval required
before any action." and hide the chat-actions warning.
- `internal/ai/eval` drops `Step.AutonomousMode`; the envelope test now
asserts the field is absent.
- `scripts/check-patrol-assistant-journey.mjs` asserts the Explain
request has no `autonomous_mode`.
- Contract prose (ai-runtime, api-contracts, agent-lifecycle,
storage-recovery, patrol-intelligence, frontend-primitives) no longer
describes `autonomous_mode:false` as a per-request override sent by the
browser. It now describes the server-set mode and the drawer disclosure.
No backend runtime change. The handler test proving a stale client's
`autonomous_mode:true` cannot elevate a request stays.
## Verification
- Private mock stack built from this branch, 1440x900 and 390x844. Every
`/api/ai/chat` body was captured with a page-level fetch wrapper.
- Plain chat with the saved level at Chat actions: body is `{prompt}`.
The pill and warning are unchanged.
- Patrol "Explain with Assistant": body keys are `handoff_context`,
`handoff_resources`, `prompt`. The drawer shows "Approval required
before any action." and "Chat: Ask first", with no warning.
- Follow-up turn: body keys are `model`, `prompt`, `session_id`, and the
disclosure persists.
- `go test ./internal/ai/eval/` passed.
- On pulse-dev: `tsc --noEmit` passed. `vitest` over
`src/components/AI`, `src/api/__tests__` and the aiChat store test
passed 2531 tests. In that parallel sweep, `AIChat.test.tsx` hit its 10s
`beforeAll` import timeout; run alone it passed 211/211.
- Read-only second-opinion review found no runtime regressions. It
flagged two contract sentences (undo/retry restoring an
"approval/autonomous override", and an overbroad "the body carries no
field"); both are fixed in this commit.
The aggregate Recovery page and its route-state owner were deleted on
2026-05-26, but RECOVERY_QUERY_PARAMS, parseRecoveryLinkSearch and
buildRecoveryRouteSearch stayed in resourceLinks.ts with no production
caller, which kept recoveryItemTypePresentation.ts alive as their only
reader. Both go.
The frontend-primitives, storage-recovery, unified-resources, alerts and
cloud-paid contracts still described the deleted page as live: its shell,
event-first workspace, section owners, filter and column models, recovery
route state, and the incident panel's link chips. Those passages now say
what exists. Recovery rows render in the Proxmox Backups and TrueNAS
Protection tabs, useRecoveryPoints decodes every recovery-points read, and
rules that only governed the deleted page are gone or reduced to a
reintroduction guard.
New assertions pin the rewritten claims: resourceLinks exports nothing
recovery-shaped, the recovery-points hook hands out normalized points and
is the only reader, the retired hooks stay deleted, each tab keeps its own
view and filter state, and the incident panel imports no route helpers.
Since 4edf5f8265, ChatRequest in internal/api/ai_handler.go has no
autonomous_mode field: Go's decoder drops it and HandleChat passes
approval-required mode into the chat service for every /api/ai/chat
request. The browser still serialized autonomous_mode from its send
options, and the eval runner kept a per-step override that sent it too,
so both read as if the client chose the execution mode while the server
ignored it.
Drop the field from AIChatAPI.chat, SendMessageOptions,
ChatMessageRequestContext and the clone, retry and restore helpers, and
Step.AutonomousMode from internal/ai/eval. aiChatStore.context's
autonomousMode stays as a drawer-only flag, so scoped handoffs still show
"Ask first" and "Approval required before any action." Tests now assert
the request carries no autonomous_mode; the backend test proving a stale
client's autonomous_mode:true cannot elevate a request stays.
Contract prose that described autonomous_mode:false as a per-request
override set by the browser now describes the server-set mode and the
drawer disclosure.
GET /api/charts/workloads-summary has had no first-party caller since
the v6 import: ChartsAPI.getWorkloadsSummaryCharts arrived with it and
nothing ever called it. GET /api/charts/storage-summary lost its last
reader when storageSummaryTrendCache.ts was deleted on 2026-05-26.
Both still cost something. The workloads route carried a payload cache,
an SLO test in the release latency gate and a benchmark, and mock mode
rebuilt the 24-hour storage aggregate on every sampler tick for a cache
nothing read.
No stable release ever requested either route from the browser: v6.0.0
and later carry no caller, and the storage trend reader shipped only in
v6.0.0-rc.1 to rc.5. Neither route is in docs/API.md (a list of common
endpoints, not a complete one), and pulse-pro, pulse-mobile and
pulse-dev-infra never call them. A monitoring:read token could reach
them, so a private script may notice, but nothing known depends on
them. pulse-enterprise names the SLO test only as fixture data, and its
latency gate passes a test that is absent from the candidate as
removed. So the routes go with their clients:
handlers, response types, the monitor trend helpers, the mock aggregate
and its prewarm, the SLO budget, the benchmark, and the ChartsAPI
methods and types. Requests to either path now get 404.
buildMockWorkloadMetricHistorySeries goes too, because the
workloads-summary handler was its only production caller.
A TypeScript reference graph from src/index.tsx finds no production
reference to the removed methods or types. The production build loses
only the two static methods from the ChartsAPI chunk (326 B raw, 49 B
gzip); every other file differs only by chunk hashes and SRI values.
deadcode ./cmd/pulse and golangci unused report nothing newly
unreachable. On private mock stacks the Proxmox overview in Trends, a
guest History drawer and the storage page make the same chart requests
before and after at 1440 and 390 px, and neither build requested the
removed routes.
Contract prose that still described either route as live now records
the retirement, and the workload chart guard pins the per-type metric
sets the surviving /api/charts/workloads route requests.
Preserve exact upstream history present at coordinator launch. Later upstream refresh remains with the protected publication gate.
Change-source: pulse-maintainer
The Patrol page fetched `GET /api/ai/intelligence/correlations` on every
mount, every 60 s poll, every return to the tab and on Retry, and the
store's dashboard bundle fetched it again for FindingsPanel's refresh.
Nothing has read the list since the Patrol supporting-context block was
removed in 27ba85ddc6; the resource drawer reads per-resource
correlations from the resource-intelligence payload.
## Change
- `stores/aiIntelligence.ts`: remove the correlation signal,
`correlations` getter, `correlationsSignal`, `loadCorrelations()`, its
org-switch reset, and its entry in `loadDashboardData()`.
- `features/patrol/usePatrolIntelligenceState.ts`: drop the read from
the Retry path's background supporting reads.
- `api/ai.ts`: remove `AIAPI.getCorrelations` (no other caller). The
backend route stays as an API surface behind `ai:execute`.
- Tests: the store bundle test now pins every AIAPI read the bundle
makes; the manual-run test stalls the post-start refresh through a
remaining read so it still proves "Starting" clears without waiting
(negative controls: awaiting the refresh, or removing it, both fail the
test). Dead correlation route stubs removed from six E2E specs.
- Contract prose in api-contracts, patrol-intelligence,
unified-resources, ai-runtime and frontend-primitives no longer says the
Patrol page or store loads or renders correlations or recent changes.
The remaining stale Patrol correlation paragraphs (patrol-intelligence
~L2135/~L2197, ai-runtime ~L6854) are rewritten by #2674; the two PRs
split hunks by agreement and merge cleanly together.
`CorrelationsResponse` stays for now:
`patrolInvestigationContextModel.ts` still imports it until #2674 lands.
It goes in a follow-up once both are on main.
## Evidence
- Demo-mode stack, headless Chromium at 1440x900 and 390x844:
correlation requests go from 1 to 0 per Patrol load, browser reload, 60
s poll and tab hide/show. Every other `/api/ai/*` read per phase is
identical; no console errors. Re-run on the final base.
- Production build: the route string is gone from the bundle;
`check-bundle-size.mjs` passes with no chunk renamed (JS gzip -0.18 kB
on the same base).
- `tsc --noEmit` passes; vitest for src/stores, src/api,
src/features/patrol, src/components/AI and the Patrol page test passes;
contract, registry and status audits pass.
Additively incorporate the batch-start upstream export cleanup without changing accepted commit identities. Retain monitoring, recovery and help integrations for validation and protected publication.
Change-source: pulse-maintainer
The Patrol page fetched GET /api/ai/circuit/status on every load, every
60 s poll, every return to the tab, and again in the background after
Retry. Its only reader was the circuitBreakerStatus memo in
usePatrolIntelligenceState, which nothing read and which the previous
commit removed. Nothing else renders breaker state: an open breaker
already reaches the page through /api/ai/patrol/status as the
circuit_open blocked cause, and the paused banner shows its reason.
Remove the store's breaker signal, getter, signal export, loader,
org-switch reset and dashboard-bundle entry, the hook's background read,
and AIAPI.getCircuitBreakerStatus with its CircuitBreakerStatus type.
The client's unit tests, the store and page test mocks, and seven E2E
route stubs go with them. The store bundle test still pins the exact
AIAPI read set. The backend route stays for API users and diagnostics.
Production builds before and after differ only in the removed code
(check-bundle-size passes). A Playwright pass on private demo stacks at
1440 and 390 px shows the breaker read going from 1-2 requests per
phase to none across Patrol load, tab hide/show and stale-data Retry,
with every other endpoint and the Assistant handoff unchanged. A forced
circuit_open Patrol status still shows "Patrol paused / circuit breaker
is open" on both builds.
The Patrol page fetched GET /api/ai/intelligence/correlations on every
mount, every 60 s poll, every return to the tab and on Retry, and the
store's dashboard bundle fetched it again for FindingsPanel's refresh.
Nothing has read that list since the Patrol supporting-context block was
removed in 27ba85ddc6; the resource drawer reads per-resource
correlations from the resource-intelligence payload instead.
Remove the store's correlation signal, getter and loader, drop the read
from the Patrol background refresh and the dashboard bundle, and remove
AIAPI.getCorrelations, which had no other caller. The backend route
stays as an API surface. Dead route stubs go from six E2E specs, and
contract prose that said the Patrol page loads or renders correlations
or recent changes is corrected.
Measured on a demo-mode stack: correlation requests fall from one to
zero per Patrol load, reload, 60 s poll and tab return at 1440x900 and
390x844, with every other AI read unchanged. Production bundle JS gzip
drops 0.18 kB with no chunk renamed.
The May cleanup deleted InfrastructureSummary, StorageSummary, the shared
summary table and the SummaryMetricCard, InteractiveSparkline and
DensityMap primitives, but left the helpers only they called. Each one
below has had no production caller since then and survived only through
its own tests and raw-source guard pins:
- getMetricsChartKeyCandidatesFromResource (agentResources.ts)
- getNormalizedIdentityLookupVariants and getAgentLikeIdentityAliases
(resourceIdentity.ts); the variants helper was briefly reused for PBS
history and dropped again on 2026-09-30
- getPBSStatusIndicator (status.ts)
- useSummaryContextualFocusState (contextualFocus.ts), which takes
filterSummarySeriesByGroupScope, resolveSummaryCardInteractionState and
SummaryCardInteractionState with it
- the deprecated ChartsAPI.getInfrastructureCharts delegate; its branch
tests now exercise getInfrastructureSummaryCharts directly
A production build differs from its parent only by that delegate's static
method; every other removal was already tree-shaken.
The frontend-primitives, storage-recovery and performance contracts still
described the deleted summary cards, sparkline, density map and summary
panel as live owners, and credited contextualFocus.ts with the removed
hook's series filtering. Those clauses now name the live owners
(summaryCardInteraction.ts behind summaryTableFocus.ts, the storage
summary cache behind the pool Growth column, TooltipPortal for chart
tooltips), become conditional on a summary strip returning, or go.
Provide manual-rule list, exact scoped confirmation, one deletion and readback inside the existing Activity flow. Invalidate stale tenant/access context and uncertain outcomes without altering finding Reopen or server permission boundaries.
Fixes#2528
Change-source: pulse-maintainer
Adding the Pulse Agent to a Proxmox cluster member meant generating a
token in the UI and pasting it into an interactive prompt on the host,
even though Pulse already knows the cluster and has an agent on its
siblings. The server-side cluster deploy that avoids this was still
shipped, but its wizard was deleted as an orphan in May, and it could
not reach members discovered by hostname: targets were parsed from the
API URL, so a name like "delly2" hit the agent's literal-IP guard and
failed preflight.
Deploy targets now resolve to a literal IP in the member's API URL or
its cluster endpoint's effective IP, matched on the member's own
connection, and members with no known address come back undeployable
with a reason. The Settings > Infrastructure install actions open one
dialog that checks, installs, and confirms each node is reporting,
falling back to the scoped installer when no sibling agent is
connected or a node cannot be reached.
The deploy routes are keyed on the cluster name, so they now refuse a
name shared by two connections instead of merging two sites, and job
creation admits one install per cluster at a time under a lock, which
lets the dialog keep a run going after it is closed.
The agent-lifecycle, api-contracts, frontend-primitives, and
storage-recovery contracts record the new install path, its address
rule, and the persisted, time-bounded admission check.
Reject mismatched resource, range and metric envelopes and malformed samples at the shared client boundary. Preserve explicit Kubernetes compatibility, real access failures, valid empty history and measured zero. Exercise drawer, single-metric and row-cache consumers, and inspect desktop and phone-emulated browser renders.
Change-source: pulse-maintainer
Withdraw another target’s service and URL while its replacement is loading or unavailable. Preserve final access status without denied bodies, label same-target transient evidence and offer an in-place retry. Dispose per-guest forms and the replaced discovery consumer without cancelling or repeating server operations. Preserve existing same-target query reuse and independent saved operator URLs. Add connected identity/access regressions, contracts and exact-content desktop/phone browser evidence.
Change-source: pulse-maintainer
GHSA-vfj7-8cjw-p6xm affects every braces release and has no fix, so the
required frontend audit failed with seven high findings on any pull
request that changes the dependency graph. Tailwind CSS 3 (chokidar,
fast-glob, micromatch) and jscpd 4 (@jscpd/finder, fast-glob) were the
only paths in, both dev-only, and no override can remove braces from
fast-glob or micromatch. Tailwind CSS 4.3.3 through @tailwindcss/vite and
jscpd 5.4.0 leave none; npm audit over the full graph reports zero.
The stylesheet keeps the v3 rendering. It pins the v3 palette (inlined,
so opacity modifiers compile to static colors), font stacks and line
heights, keeps v3's cascade order by placing former component and
unlayered rules around the generated utilities, and restores v3 hover on
touch devices, gray-200 default borders, the 3px blue ring, placeholder
color, button cursor, table-cell padding and the space-* and divide-*
child selectors. The 900/950 shades Pulse overrode with 25% alpha are
opaque now and their callers say /25, because v4 multiplies an opacity
modifier into a translucent color instead of replacing it. Opacity
modifiers on var() colors that v3 never generated are removed.
Utility order changed from lexical to numeric, so a few caller overrides
that silently lost in v3 would start to win. Touch targets keep their
authored 44px phone size (the Machines row stays 32px); cosmetic
overrides v3 never applied are removed, and overrides it did apply use
the important suffix.
Contract-Neutral: Tailwind CSS 4 migration for GHSA-vfj7-8cjw-p6xm: utility class renames and stylesheet compatibility, no subsystem contract change
The root parser's legacy no-token alias accepted a new private-file command beside a legacy download URL. Reject every mixed alias, as the existing coherent-transport contract requires. Preserve backup-permission URL bindings. Align older secret-in-command and shared type fixtures, and exercise the actual wrong server installer parser.
Contract-Neutral: Enforce the already-declared coherent bootstrap transports and correct verification fixtures; no API, scope or documented behaviour delta.
Change-source: pulse-maintainer
Separate PVE/PBS setup and telemetry credentials from shell source and download URLs. Use silent root/sudo input, private-file handoff, complete downloads and the agent preflight; preserve scope, TLS defaults, single-line paste and coherent rolling-upgrade metadata. Reveal tokens through the existing dialog and discard late issuance after close. Pin executable shell, history, TLS/registration and browser contracts without using real credentials.
Change-source: pulse-maintainer
Expose the existing admin audit escape hatch in the shared Actions review with direct-state confirmation, fresh re-read, and inconclusive outcome presentation. Keep server authorization and transport truth unchanged. Record affected canonical contracts and fresh desktop/phone browser verification in the same candidate.
Change-source: pulse-maintainer
A failed update check never reaches the update history, because nothing
was applied, so telemetry could not tell an install whose check is broken
from one that was offered an update and ignored it. The 6.4.3-rc.1 installs
looked healthy while their check failed on every call (#2282).
The update manager now records a closed outcome for every check on the
install's effective channel (up_to_date, available, no_release,
rate_limited, network_error, metadata_error, skipped, error), tagging
lookup failures without changing their error text, and ignores previews of
another channel from Settings. Telemetry schema v18 exports update_channel,
update_check_outcome and update_available. No version, URL or error text
leaves the install. The Settings preview type, privacy disclosure, adoption
report and subsystem contracts are updated to match; the Pulse Pro receiver
change lands separately and the parity check passes against it.
Resolve the PR2087 frontend formatting gate without changing severity behavior or rewriting the reviewed source. Re-run the production-component browser matrix and refresh its content hashes; whole-tree formatting and local TypeScript checking pass.
Change-source: pulse-maintainer
Retain warning severity through email API save/reload and webhook editing instead of coercing it to all. Add adapter round-trip and webhook edit/save regressions for issue #2069 with alerts, API and notifications contracts. Browser verification covers production components at desktop and narrow widths using synthetic settings; backend filtering is unchanged.
Change-source: pulse-maintainer
Manual checks previously bypassed only the browser cache, so a newly
published preview could remain hidden behind a fresh-looking server result.
Carry explicit freshness to the provider, retain prior evidence on failure,
and omit unknown dates instead of rendering year one.
Live funded qualification found hidden tool results and misleading action
submission outcomes. Share the result-bearing transcript across stored chat
and product history, render the retained evidence, and distinguish captured
proposals from broker acceptance. Keep review usable while Patrol is paused.
Record Gemini route pricing and exact qualification limits. Integrate current
main and repeat browser proof for the incoming login flow. Approved/rejected
recovery remains unqualified without the development command agent.
The 2026-09-01 Patrol assessment could not answer four questions from
telemetry: what share of Patrol installs run local versus cloud models,
what Patrol costs an install per month (issue 1789 estimates ~104k input
tokens per full run at ~5.5 runs a day, above the Pro fee on non-Flash
tiers), how the 240 of 255 fleet investigations that produced no plan
actually ended, and which effective Patrol mode an install runs.
Schema v17 adds closed buckets only. ai_provider_class classifies the
Patrol model route (none, local, cloud_byok, cloud_subscription,
hosted_quickstart, unknown) with a syntactic private-host check on custom
OpenAI-compatible endpoints; it never resolves DNS or carries a provider,
model, endpoint, or account. pulse_intelligence_patrol_autonomy_level is
the effective level after licence and Autopilot gating. The two token
fields bucket the existing local usage ledger's Patrol events, so exact
counts and prices stay on the install. Thirteen investigation outcome
counters partition the findings already counted as investigated, one
bucket per finding, adding no finding, resource, or session identity.
The strings are never omitted, so an empty value can only mean a pre-v17
sender; the receiver stores those as unknown. PRIVACY.md, its shipped
mirror, the Settings preview interface, and the security-privacy and
api-contracts subsystem notes carry the disclosure, and the coverage gap
is registered in status.json.
The agent-lifecycle and storage-recovery contracts name internal/api under
their extension points, so both record that this telemetry is adjacent
adoption analytics and never agent or storage state. The stable E2E tier
pins the public schema version, so the disclosure spec moves to 17, and
the Settings preview interface change carries a browser receipt from a
scratch build of this tree at 1280x800 and 390x844. status.json also drops
an identical duplicate of the ai-provider-guided-setup coverage-gap record
that two merges each appended, which the private governance audit rejects.
The bucket for a legacy config still pointing at the retired Pulse-hosted
route is named hosted_legacy: the shipped privacy document discloses the
vocabulary verbatim and the frontend copy contract keeps that document
free of the retired hosted quickstart wording, so the telemetry package
now pins the vocabulary at the source.
A paying customer had no place in Pulse that added up Patrol's work: runs
were per run, findings per finding, actions under Actions, spend on the AI
cost dashboard. The Activity tab now opens with a "This week" card that
reads GET /api/ai/patrol/digest and shows six things in plain language:
Patrol runs, new issues and how many are still open, issues resolved,
investigations, fixes run, and estimated spend. Tile copy is mode-aware, so
a watch-only install reads that nothing was investigated because Patrol is
watch only, and the card links to Actions only when Patrol-origin fixes are
waiting for approval.
The card never recomputes counts client-side and keeps forensic vocabulary
out; a failed load says the summary is unavailable rather than showing
zeros, and a truncated run history says since when the numbers hold. The
page header already states the Patrol mode sentence, so the card does not
repeat it. Second slice of the "Patrol weekly digest" named bet in the
pulse-pro demand ledger; browser proof in browser-verification.json covers
the Activity tab at 1280 and 375 pixels against an isolated mock-mode
backend.
Forty-two percent of paying installs never get Patrol running, and the
support and issue evidence says the wall is model choice and cost: a
prospect asked which models Pulse recommends before trying, a Pro customer
picked Flash-Lite to save money and Patrol then could not file verdicts,
and a mispriced Opus row tripped the 20 USD budget so the circuit breaker
disabled Patrol with nothing but a log line (#1789).
Answer both questions at the point of choice. The Patrol and shared
default model pickers pin guided models in a "Suggested for Patrol"
section with recommended / suggested / caution markers and a one-line
reason: the Ollama preflight blessing is the only recommended entry,
Gemini Flash-Lite the only caution, and cloud starting points are labelled
price-driven and unqualified until this install's own readiness pass
upgrades them to "Verified on this install". A server-computed cost
preview sits under the model choice: monthly estimate from Pulse's price
table, the schedule, and the install's own median full run once three
priced runs exist (a measured 104,528-in / 4,491-out run otherwise), with
the assumption stated and a token explained once, plus 30-day spend
against budget. Each schedule preset is priced in the Schedule select.
When a per-token model is picked while the schedule is still the 6-hour
default, the cost model proposes the slowest preset that keeps scheduled
runs under half the budget (20 USD reference when none is set) and says
what the slower check costs in detection delay; a schedule the install
already chose is never changed.
A used-up budget is now a spending decision, not a provider fault: the
budget refusal is a typed sentinel with the figures, classified as the
budget_exhausted cause, excluded from circuit-breaker accounting, and
promoted into the Patrol block state, so the Patrol page shows "Patrol
paused" with the spend and limit and a "Raise the cost budget" action
instead of "Check Patrol model".
Two users (discussions #1623, #1699) could not find Patrol's durable
outcomes: the Needs attention detail offered only Acknowledge and
Suppress, and Remember as expected, Dismiss: Not an issue, Dismiss:
Later, and Create rule sat two levels below it. The same screenshot
showed an alert with eleven open/resolved transitions in a day listed as
eleven timeline rows, and Patrol findings restating an active alert
appearing beside it. Telemetry shows most findings are seen and ignored.
Backend: findings_alert_mirror.go is the single matcher for a finding
that restates an active alert (explicit alert identifier, or same
canonical resource plus the same condition class). Patrol stamps
mirrors_alert_id/mirrors_alert_type after every real and demo cycle and
clears them when the alert resolves; the finding is kept, not dropped.
The storm throttler now also owns flap detection: four or more
open/resolved transitions in 24h mark a finding flapping, and the store
maintains one collapsed lifecycle row with the count instead of a row per
transition, hydrating the window from persisted lifecycle after restart.
The attention projection applies the same threshold to alert timelines
and exposes a flapping summary while keeping the full timeline.
Frontend: the attention detail gains a Lasting decisions section that
offers the four Patrol decisions on the finding mirroring the alert, each
with one line saying what it does and how long it lasts, an inline
confirmation, and a remembered-decision state with Reopen. An alert with
no mirrored finding says Patrol has nothing to remember and points at
alert thresholds. Flapping items carry one label and a summarised
timeline with every transition under an expansion. FindingsPanel demotes
active alert-mirroring findings into a collapsed group. The Patrol
surface loads dismissed findings so remembered decisions are visible.