Commit Graph

3634 Commits

Author SHA1 Message Date
rcourtman 2f967406df perf(ai): gate and batch Patrol's per-alert auto-resolve model calls
Production telemetry (30d) shows Patrol averaging ~9.4 billed AI calls
per run while only ~1-2 come from the analysis passes. The remainder is
the alert auto-resolve review: one QuickAnalysis call per active alert
(>=10min old) on every run, re-asking the model about the same standing
alert every interval even when the trigger condition demonstrably still
holds.

Two structural changes, both resolution-neutral:

- Still-firing gate: skip the model review for alerts whose current
  snapshot still shows the trigger condition (metric at/above threshold,
  offline resource still offline). A correct review could only answer
  KEEP, so the question is not worth a billed call. The gate never
  resolves anything locally - uncertain cases (unknown types, missing
  resources, unmapped metrics) still go to the model, which retains
  sole authority over resolution.
- Batched review: the remaining candidates are reviewed in one model
  call per 20 alerts (numbered verdict lines, unparseable -> KEEP,
  bare-RESOLVE fallback for single-alert batches) instead of one call
  per alert.

Steady-state runs with standing alerts drop from N review calls to 0;
runs where conditions may have cleared pay ceil(K/20) instead of K.
QuickAnalysisRequest gains a TargetType tag (recorded on the usage
event) so cost telemetry can decompose alert-review spend from the
main patrol pass going forward. Contract updated in-commit
(ai-runtime: model-owned resolve direction, cost-gated keep direction,
batched review, fail-safe parsing, alert_autoresolve usage tagging).
2026-07-17 23:23:38 +01:00
rcourtman 6351fd6526 fix(updates): select releases by highest version, not GitHub list order
Production telemetry (7d, 2026-07-17) shows a persistent tail of live
installs on 6.0.0-rc.x and 6.0.3/6.0.4 while 6.0.5 has been stable since
2026-07-09. Root cause for the reachable stranding class: release
selection trusted GitHub's created_at ordering, and this repo interleaves
v5-line maintenance releases with v6 releases (v5.1.36 was created the
day before v6.0.5). Whenever a v5.1.x release is the most recently
created stable:

- stable channel (getLatestReleaseForChannel) returned the first
  non-prerelease in list order -> "no update" for every v6 install
  until the next v6 release ships
- the RSS rate-limit fallback returned the first feed entry matching the
  channel, same failure, both channels
- the unattended updater trusted /releases/latest, which GitHub defines
  as most-recently-created stable -> timer no-ops in the same window

All three now select the highest eligible version: the API path tracks
newest stable + newest prerelease in one pass (prerelease = GitHub flag
OR tag shape), the feed fallback picks the max matching the channel, and
pulse-auto-update.sh scans /releases pairing each tag with its own
draft/prerelease flags before the fail-closed shape filter, keeping
/releases/latest as fallback. helm-chart-* tags fail semver parsing and
are dropped everywhere.

Verified not broken (pinned by new tests): rc-channel installs are
offered the newer stable (6.0.0-rc.6 -> 6.0.5) and move onto the next rc
line when one opens (-> 6.1.0-rc.2); the auto-update prerelease filter
(fail-closed is_prerelease_tag, metadata-flag refusal) still refuses
prerelease targets on stable. Go tests fail on the pre-fix code
(returned 5.1.37 / "no update").

Contract: deployment-installability now pins version-max release
selection with proofs in manager_stranded_upgrade_test.go and
pulse_auto_update_test.go.
2026-07-17 23:21:49 +01:00
rcourtman 817aaeabc1 feat(ai): surface the Assistant at first AI setup and fix blessed-path readiness
Telemetry shows thousands of installs configure an AI provider but almost
none ever use the interactive Assistant. A live first-session exercise
(fresh install, Ollama qwen3:8b quickstart) found why: after enabling,
nothing changes on screen — the launcher and handoff buttons are gated on
sessionCapabilities.assistantEnabled, which was only read at page load;
the empty transcript was blank; and the blessed Ollama+qwen3:8b path
reported Patrol degraded while telling the user to pull the model they
had just selected.

- Setup-modal success now opens the Assistant drawer, and the AI settings
  save paths refresh the assistantEnabled capability in place
  (aiChatStore.refreshEnabledFromServer) so entry points appear without a
  reload; toasts point at the Assistant instead of back at settings.
- The empty transcript owns a plain-language welcome and three suggested
  prompts that dispatch as real turns (ASSISTANT_SUGGESTED_PROMPTS).
- Patrol static readiness: the blessed Ollama Patrol model is Ready;
  other Ollama models keep the warning, now naming the selected model.
- pulse_summarize fleet argument errors instruct the model to enumerate
  resources itself instead of interrogating the operator (observed live:
  'how is my machine doing?' ended in a resource-ID elicitation).

Contracts: ai-runtime and frontend-primitives Current State updated.
Tests: full internal/ai + internal/api suites green; vitest ChatMessages,
AISettings, aiChat store, and settingsArchitecture suites green; flow
verified live end-to-end.
2026-07-17 23:20:44 +01:00
rcourtman 83909bd19a feat(patrol,actions): route Watch-only installs toward their first governed action
Production telemetry (30d) shows the Pulse Intelligence funnel collapsing at
governed actions: 332 installs enabled chat actions but only 23 created an
action plan. Root cause: both action dials default off, and an install in the
default Watch only mode has no forward path — findings on unlocked installs
show neither an Investigate button (gated on non-monitor modes) nor any nudge
(the Pro handoff only renders when the capability is plan-locked), and the
empty Actions inbox never says why it is empty.

- Patrol findings on unlocked Watch-only installs now offer 'Switch to Ask
  first' in the expanded row (same slot as the Pro handoff), driving
  handleAutonomyChange('approval') so the Investigate action appears in place.
- The empty Actions Open tab now explains that Watch only never queues fixes
  and points at the Patrol mode switch, or names the Pro capability on locked
  installs (presentation-policy aware, upgrade link suppressed with
  hideUpgrade).
- Contracts updated: patrol-intelligence (expanded-row forward-path rule),
  unified-resources (Actions calm-state producer explanation), api-contracts
  (canonical autonomy read/save reuse), frontend-primitives (guidance stays in
  governed presentation helpers).

Verified live on a scratch instance (locked drawer keeps the Pro handoff with
no nudge; empty inbox renders the locked explanation) plus DOM tests for the
unlocked slot, which needs the Pro runtime to exercise live.
2026-07-17 23:09:31 +01:00
rcourtman 0f15de6a4a fix(telemetry): suppress outbound pings while mock mode is enabled
Every mock-mode boot (e2e/CI managed backends, qual runs, demo containers)
was sending startup/heartbeat pings describing the synthetic fixture fleet,
registering as real installs in the license-server telemetry DB and
dominating the VMware/Kubernetes/TrueNAS adoption aggregates (346 of 6,225
weekly-active installs matched the mock signature, incl. 120 of 227 on
6.1.0-rc.1).

- internal/telemetry: route startup and heartbeat sends through sendEvent,
  which drops the ping while mock.IsMockEnabled() is true; checked per event
  so runtime mock toggles take effect immediately. Disclosed in the package
  doc and both PRIVACY.md copies.
- scripts/telemetry_adoption_report.py: exclude historical mock-fleet rows
  (kubernetes_pods = 120×N with vmware_hosts = 7×N, the internal/mock
  fixture template) by default, report the excluded row/install counts, and
  add --include-mock-fleet to audit them. Versions through 6.1.0-rc.2 keep
  pinging the mock fleet until upgraded, so report-side filtering stays
  necessary.
- security-privacy contract: record the mock-mode suppression boundary and
  the report-side mock-fleet exclusion as subsystem invariants.
- Tests: Go sendEvent suppression/sending pair against an httptest endpoint;
  Python signature-matcher and summarize_rows exclusion coverage.
2026-07-17 18:19:18 +01:00
rcourtman 7ab605452d fix(release): fail closed on malformed release notes 2026-07-17 11:12:22 +01:00
rcourtman 909153a398 Close v6.1.0-rc.2 release claim 2026-07-17 02:17:10 +01:00
rcourtman 2d38cfb9ef Stabilize legacy license exchange test 2026-07-17 00:27:56 +01:00
rcourtman a857c7e783 Prepare v6.1.0-rc.2 release 2026-07-16 23:45:31 +01:00
rcourtman e384c2d762 Release Patrol finding completion claim 2026-07-16 23:06:10 +01:00
rcourtman e23448df7b Allow Patrol to record sequential findings 2026-07-16 22:50:11 +01:00
rcourtman 82d971d8f0 Reserve Patrol finding completion fix 2026-07-16 22:39:57 +01:00
rcourtman b782cf1165 Release Patrol restart-loop qualification claim 2026-07-16 21:04:56 +01:00
rcourtman 72c409743d Warn when two machines share one Docker agent identity
Cloned VMs that keep the same /etc/machine-id collapse into a single
Docker host in Pulse, with each clone's report silently overwriting the
other (#1584). The server now tracks per-identity hostname and machine
ID observations and flags a conflict when a value flaps back to one
already seen inside a 15-minute window, a signature a one-time rename
never produces. The conflict rides the DockerHost model through unified
resources, and the Docker page shows a warning naming the flapping
hostnames with the machine-id remedy. The warning self-clears once one
clone stops reporting for the window.
2026-07-16 20:55:09 +01:00
rcourtman 4d80e53588 Align restart-loop qualification with Patrol threshold 2026-07-16 20:53:02 +01:00
rcourtman aff5bb4e93 Reserve Patrol restart-loop qualification alignment 2026-07-16 20:50:57 +01:00
rcourtman cd050c2f18 Release Patrol replay compatibility work claim 2026-07-16 20:32:57 +01:00
rcourtman cae8477128 Preserve Patrol replay safety outcomes 2026-07-16 20:30:37 +01:00
rcourtman d69d56ff74 Reserve Patrol replay compatibility work 2026-07-16 20:22:57 +01:00
rcourtman 53ee6b86e7 Release Patrol inference allowance work claim 2026-07-16 20:03:27 +01:00
rcourtman afbcd3023c Bound automatic Patrol inference allowances 2026-07-16 19:55:44 +01:00
rcourtman e07e103e42 Reserve Patrol inference allowance work 2026-07-16 19:35:19 +01:00
rcourtman f68d5be4b8 Release Pro investigation efficiency work claim 2026-07-16 18:21:56 +01:00
rcourtman 676117bf27 Separate Patrol evidence and model-turn budgets 2026-07-16 17:50:08 +01:00
rcourtman 359147b771 Reserve Pro investigation efficiency work 2026-07-16 17:22:18 +01:00
rcourtman 91eaa82f2c Release Patrol qualification work claim 2026-07-16 17:18:32 +01:00
rcourtman b6e5f4d512 Align remediation qualification with Patrol policy 2026-07-16 17:01:37 +01:00
rcourtman b905bc83ea Renew Patrol qualification work claim 2026-07-16 16:37:09 +01:00
rcourtman 1397f72c1f Propagate Basic auth identity into action authorization 2026-07-16 16:34:26 +01:00
rcourtman b91ba759c9 Route Claude native tool intent through Pulse 2026-07-16 16:09:48 +01:00
rcourtman a266a2e577 Renew Patrol qualification work claim 2026-07-16 15:50:10 +01:00
rcourtman d162110233 Make remediation fixtures model-neutral 2026-07-16 15:44:19 +01:00
rcourtman df59f210e5 Prevent mixed-auth configuration deadlock 2026-07-16 15:25:47 +01:00
rcourtman 43e70411a6 Normalize bounded Docker action clock skew 2026-07-16 14:36:01 +01:00
rcourtman cde850c686 Isolate Patrol investigation dispatch from Watch runs 2026-07-16 14:06:01 +01:00
rcourtman 3291d97a14 Reserve Patrol investigation isolation work 2026-07-16 13:57:56 +01:00
rcourtman 62f1986d0c Release Patrol subscription schema claim 2026-07-16 13:33:44 +01:00
rcourtman e1e1048e08 Preserve durable Patrol outcomes across Claude wrapper retries 2026-07-16 12:50:15 +01:00
rcourtman 6c8ecd2284 Constrain plain Claude completion fallback 2026-07-16 12:45:24 +01:00
rcourtman 1439495e70 Accept safe plain Claude completion turns 2026-07-16 12:42:12 +01:00
rcourtman 043ecf4c66 Stabilize Claude subscription completion transport 2026-07-16 12:38:19 +01:00
rcourtman 0719e34255 Bind subscription tool calls to native schemas 2026-07-16 12:29:14 +01:00
rcourtman 4f42632cd6 Transition Patrol claim to subscription object schemas 2026-07-16 12:26:10 +01:00
rcourtman fddcc3fefe Honor Patrol lifecycle tool ordering 2026-07-16 11:39:23 +01:00
rcourtman b40b82a07b Transition Patrol claim to lifecycle ordering 2026-07-16 11:31:10 +01:00
rcourtman 07cbdd792b Separate subscription adapter trust boundaries 2026-07-16 10:44:53 +01:00
rcourtman b83a6a6457 Reserve Patrol subscription trust-boundary work 2026-07-16 10:39:16 +01:00
rcourtman 0f8d61a696 Release Patrol subscription timeout claim 2026-07-16 10:37:13 +01:00
rcourtman cdae56df0f Align qualification wait with subscription preflight 2026-07-16 10:05:20 +01:00
rcourtman b84f650e77 Allow bounded subscription-agent preflights 2026-07-16 09:54:34 +01:00