Commit Graph

4371 Commits

Author SHA1 Message Date
rcourtman 8ea94d792b Add the Patrol weekly digest endpoint
Nothing in Pulse showed a paying customer what Patrol had done for them:
about 164 runs a month per install, findings raised, investigations and
fixes, and none of it summarised. GET /api/ai/patrol/digest rolls the last
N days (default 7, max 30) up from records Pulse already keeps: run
history, the findings store, Patrol-origin action audits, and the usage
cost store. It adds no telemetry and persists nothing. The payload
reports when the bounded run history no longer covers the window and when
model pricing is unknown, rather than quietly under-counting.

This is the first slice of the "Patrol weekly digest" named bet in the
pulse-pro demand ledger; the in-app "This week" card follows once its
browser pass is recorded. docs/PATROL_WEEKLY_DIGEST.md holds the design
note and the honest limits of each line.

status.json registers the patrol-value-visibility coverage gap, the
candidate lane, and its work claim. It also drops the second, identical
copy of the ai-provider-guided-setup coverage gap that landed with #1853;
the duplicate id fails the status audit on main for every pull request.
2026-09-02 08:53:50 +01:00
rcourtman ac328e1eee Guide the Patrol model choice with cost preview and budget pause
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".
2026-09-02 07:55:42 +01:00
rcourtman a10c175f31 Fold alert-mirroring findings, collapse flapping, surface lasting decisions
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.
2026-09-02 07:02:51 +01:00
pulse-triage[bot] d680c339d9 Require accessible names for shared dialogs
Make the shared Dialog prop contract require exactly one accessible-name strategy so new modal call sites cannot silently omit screen-reader context. Record the canonical primitive contract, preserve existing runtime behavior, and cover both valid strategies plus invalid unnamed and ambiguous props.

Change-source: pulse-maintainer
2026-09-02 04:40:19 +01:00
pulse-triage[bot] 9edc8bcea3 Merge captured Pulse upstream
Incorporate the batch-start upstream rootful qualification fixes without re-parenting canonical maintainer commits.

Change-source: pulse-maintainer
2026-09-02 03:48:55 +01:00
rcourtman e93ee38084 Tighten rootful runtime cleanup proof
Rootful evidence must reject non-running systemd states and prove no unrelated container runtime survives. Require exact manager state, mask distro containerd, recheck readiness after scenarios, and remove the complete Podman socket boundary.
2026-09-02 02:52:13 +01:00
rcourtman e836dfda13 Fix rootful qualification readiness
The disposable host reached multi-user.target in a degraded state because distro Podman housekeeping used overlay storage inside the outer container. Mask unused units, force VFS for explicit daemons, and fail closed on systemd unit failures so retained rootful evidence remains trustworthy.
2026-09-02 02:43:30 +01:00
pulse-triage[bot] f503b13442 Bind release dispatches to the admitted commit
A workflow dispatch by branch can resolve after that branch moves, allowing an unreviewed tip to enter the release pipeline. Require every publishing dispatch to name its expected source SHA and make the workflow reject a different source or workflow commit before checkout.

Change-source: pulse-maintainer
(cherry picked from commit a461fc9c0a)
2026-09-02 02:28:14 +01:00
rcourtman 56e51e622e Merge pull request #1825 from rcourtman/topic/release-train
Adopt the release train: promote the soaked candidate, not the branch tip
2026-09-02 02:08:32 +01:00
rcourtman 5f0a487404 Merge pull request #1823 from rcourtman/pulse/secure-runtime-rootful-qualification
Add rootful runtime qualification packet
2026-09-02 01:48:16 +01:00
rcourtman df7ad9be43 Adopt the release train: promote the soaked candidate, not the branch tip
Stable promotions built whatever the dispatch branch was at that second.
The resolver checked that HEAD descends from the promoted release
candidate but never that its content matches, so v6.4.0 shipped 64
changed files, including product code, that v6.4.0-rc.12 had not
soaked. Every v6 version was mapped to main, which now moves every few
minutes under the autonomous maintainer, so each fix to a candidate
brought everything landed since and stable was never an exact soaked
commit. Five of six stable minor releases shipped under version-bound
owner exceptions that waived the soak.

From v6.5.0 the release train applies (RELEASE_PROMOTION_POLICY.md,
"Release Train"): a two-week train sized to measured velocity, a
release/v6.N branch per train declared in the control plane so the
workflow refuses a dispatch from anywhere else, a stable promotion that
may differ from its candidate only in release metadata unless
hotfix_exception names active customer harm, and a seven day soak for
minor releases. The 6.4.x line stays on main so the v6.4.3-rc.1
candidate already prepared there is unaffected. The gap is registered
as coverage gap release-train-exact-candidate-promotion.
2026-09-02 01:17:38 +01:00
rcourtman 7996848b64 Submit advertised lifecycle actions through pulse_control, not prose
A paying operator asked the Assistant to reboot five Proxmox VMs matching
a name pattern (GitHub #1782, support mail 2026-08-26 and 2026-08-29). The
model resolved the VMs and then ended with a report that invented a
prerequisite: a QEMU guest agent on 6.3.2, a "discovery binding" on stable
6.4.0. It never planned the action. Three defects made the governed path
fail whenever the model did try it, and nothing refused the prose ending
when it did not:

- pulse_control handed the session-scoped id (vm:<node>:<vmid>) to the
  action lifecycle, whose registry keys on canonical unified ids, so a
  Proxmox guest plan could never resolve.
- pulse_control gated the action on the legacy per-executor action list,
  which never carried the canonical "reboot" capability Proxmox guests
  advertise, so "reboot" was refused as not permitted before planning.
- A reference absent from the session context was refused with "resource
  discovery is required" even when the unified inventory resolved it.

pulse_control now binds its target to the canonical unified resource
(session alias first, then a unique inventory match, refusing ambiguity
with candidate ids and naming the pulse_query recovery on a miss), passes
the canonical id to the planner, and answers "not available" only from
the resource's current advertised capabilities. The FSM ordering block
and the shared operating instructions state that a recoverable block is
not a limitation to report, and the instructions require the governed
action tool for advertised capabilities. The agentic loop adds a bounded
advertised-action gate: when the operator asked for a lifecycle action,
pulse_control was offered but never submitted, and a session-resolved
resource advertises the action, a tool-free final answer is refused once
with the exact per-target calls.

Covered by tools and loop unit tests (the #1782 transcript against a
scripted provider fails on the previous code with the two exact errors
above), a prompt-contract test, and the live eval scenario
ProxmoxBulkLifecycleActionScenario.
2026-09-02 00:19:08 +01:00
rcourtman 14a7d0bdaf Merge action-runner replay and Windows portability fixes 2026-09-02 00:16:26 +01:00
rcourtman d3ea482827 Merge branch 'main' into pulse/secure-runtime-rootful-qualification 2026-09-02 00:14:50 +01:00
rcourtman 2de076a9bc Merge main into claude/hostagent-replay-reconnect-wait
Pick up the browserslist advisory fix (#1827) so the required Frontend
dependency audit can pass on this pull request.
2026-09-02 00:14:41 +01:00
rcourtman 500cc1bf17 Harden rootful qualification evidence boundary 2026-09-01 23:53:36 +01:00
rcourtman c022a0dfe6 Raise browserslist above security advisory floor 2026-09-01 23:33:55 +01:00
rcourtman b1044cd8a4 Let replayed request ids wait for the in-flight handler instead of dropping
Since 60d0651a88 every typed request registers a per-connection cancellable
slot that its handler goroutine releases in a deferred cleanup after sending
its result. The server replays a request id when it wants the durable receipt
again, and that replay can reach the reader before the previous handler's
deferred release runs. launchCancellableRequest treated that as a duplicate
and dropped it, so the server waited out the operation's full timeout for a
result the agent already held. The Linux x64 native-verification leg failed
this way on 12 of the last 25 main runs, always on a "replay 1" dispatch of
host update, storage cleanup, or Docker lifecycle.

Give each slot a done channel that closes on release. A replay whose id is
still registered on the same connection now waits for that release and then
runs, answering from the durable receipt. Invalid ids and over-capacity
requests are still dropped. A unit test pins the wait-then-run behaviour and
the agent-lifecycle contract records the replay rule.
2026-09-01 23:22:55 +01:00
rcourtman 584cef81a1 Add rootful runtime qualification packet 2026-09-01 22:40:50 +01:00
pulse-triage[bot] 5fb7177b8a Require compiler dispatch run details
Change-source: pulse-maintainer
2026-09-01 22:26:46 +01:00
rcourtman 45dd45fe29 Record rootless runtime qualification evidence 2026-09-01 22:09:22 +01:00
rcourtman 62bfdbca7e Prepare v6.4.3-rc.1 release
Open the v6.4.3 candidate line from main. The v6.4.2 tag was staged on
2026-08-31 but never activated: its release run was cancelled after the
private Pro build failed the compiler memory gate, so the latest published
stable is still v6.4.1. This candidate carries the complete v6.4.2 change
set plus the corrections landed since that tag, including the stale PBS
Backup Running state (#1815), the Windows Unified Agent auto-update 404
(#1820), and shared-token same-hostname agent identity collapse (#1753).

Packet: VERSION, compose and install-docker defaults, Helm chart metadata,
release notes with a declined visual plan, changelog, pointer docs and
the shipped docs mirror, and the deployment-installability cutoff note.
Rollback target is v6.4.1 and the mobile decision is no-mobile-impact.

Tests: the packet tests now describe the 6.4.3 train, v6.4.2 is recorded
as an unpublished stable so it is never derived as the previous stable or
rollback target, and the Python v6.4.2 notes expectation matches the
phrase the notes actually use.
2026-09-01 21:37:41 +01:00
rcourtman 60041ad9e6 Validate each rootless socket identity 2026-09-01 21:26:49 +01:00
rcourtman b87ab75965 Limit planning status audit to tracked documents 2026-09-01 21:18:24 +01:00
rcourtman bc3dd3b704 Give rootless qualification hosts unique identities 2026-09-01 21:18:24 +01:00
rcourtman feff95934c Remove the Home tab and page, keep the fleet health verdict engine
The fleet health Home surface reached main on 2026-09-01 by implementing
a July agent-handoff spec with no demand-ledger entry. Exercised live on
the 1,510-resource mock estate it rendered 572 tiles on first paint with
275 in "Needs attention", 232 of them backup age, against two real
criticals; per-node storage mounts, disks, and swarm secrets appeared as
fleet members with no host context; and the critical node tile opened
its platform overview with the node below the fold. No stable release
carries it yet, so remove the route, navigation tab, page, i18n keys,
Assistant page context, route preload, and e2e cases now rather than
spend lane turns polishing an ungraded bet.

The server-side ResourceHealth verdict, its API projection, and the
/api/state/summary verdicts and attention extension stay: they are one
canonical health boundary that #1478 asked for and that any future
surface should read. The spec is parked with the 2026-09-01 review kept
as the design record.

Browser proof: desktop and 375px shells show no Home entry, /home renders
the not-found surface and its Go to workspace button lands on the
default workspace, the narrow More sheet lists Settings only, no console
errors or horizontal overflow.
2026-09-01 20:54:21 +01:00
rcourtman 2f8a4ec629 Require a Status line on planning documents
Any *_SPEC.md, *_PLAN.md, or *_CONTRACT.md under docs/ must now carry a
Status: line (or a ## Status section) in its header, enforced by the
pre-push lint. A spec, plan, or contract is a record of a decision, not
demand; the 2026-09-01 Home wall case showed what happens when a stale
handoff spec reads as a current signal. Subsystem contract markdown is
governed separately and is not scanned.

The commercial cancellation/reactivation e2e test plan gets the one
missing line so the existing set passes.
2026-09-01 20:52:25 +01:00
rcourtman f364cfc73b Reset Podman qualification storage on teardown 2026-09-01 20:49:43 +01:00
rcourtman 54cd77d3b1 Mark stale spec, plan, and contract docs with verified status
Repository specs, plans, and contracts are records of past decisions, not
demand. Each triaged document now carries a Status line that was checked
against the code and governance surfaces rather than against the document:

- VMware vCenter phase-1 onboarding, API runtime, resource projection, and
  alerts/assistant specs: IMPLEMENTED (shared floor shipped; live vCenter
  proof still outstanding).
- VMware execution plan and proof matrix: PARKED with a note that the
  first-lab-ready checkpoint was reached and the live proof has been
  blocked since 2026-03-30 with no ledger entry.
- Customer Account and Pulse Account portal specs: IMPLEMENTED at the L17
  RC floor; post-GA expansion is a lane follow-up, not a build signal.
- v6 bridge release foundation spec: SUPERSEDED by the resolved decision
  and lanes L18, L19, L20, and L22.
- Service assurance plan: slices A-E IMPLEMENTED, slices F-H PARKED.
- Availability history contract: IMPLEMENTED (history and fleet slices).
- Alert engine evolution: IMPLEMENTED; UI migration stays demand-gated.
- Legacy host classification audit: record; post-release queue PARKED.
- OIDC scope fix spec: closed record, fixes shipped in v6.0.4/v6.0.5.

Backup task timeline contract is left as ready for implementation because
its ledger entry is threshold-met. The HOME status wall spec, subsystem
contracts, and records are untouched.
2026-09-01 20:43:26 +01:00
rcourtman 832a4be9fd Record the 2026-09-01 Home wall review as binding spec revisions
Live exercise on the 1,510-resource mock estate showed the shipped Home
tab rendering 572 tiles on first paint with 275 in "Needs attention",
232 of them backup age, against two real criticals; per-node storage
mounts, disks, and swarm secrets rendered as fleet members with no host
context; and the critical node tile opened its platform overview with
the node below the fold. The implementation followed the July handoff
spec faithfully, so the fixes are recorded as spec revisions R1 to R6
(backup staleness out of attention, attention cap, fleet-member
granularity, host context, node deep links, plain-language reasons).

The default-route flip stays gated until R1 to R5 land and are
re-exercised in a browser. The spec now names its demand record in the
pulse-pro ledger and states that it is a decision record, not demand.
2026-09-01 20:35:47 +01:00
rcourtman fec53eb39e Wait for rootless runtime mounts to release 2026-09-01 20:26:46 +01:00
rcourtman 089ce2379d Clean runtime fixtures before qualification teardown 2026-09-01 20:13:15 +01:00
rcourtman 19f264701e Wait for stable rootless recovery evidence 2026-09-01 20:01:15 +01:00
rcourtman 861f0f07bc Persist rootless qualification receipts 2026-09-01 19:47:15 +01:00
rcourtman e89eaac9d4 Require VCS metadata in rootless packet builds 2026-09-01 19:37:23 +01:00
rcourtman 67dce69e73 Remove safe profile state on uninstall 2026-09-01 19:28:01 +01:00
rcourtman d99e3cc715 Log rootless admission fallback reasons 2026-09-01 18:39:23 +01:00
pulse-triage[bot] f8de8b90ce Merge remote-tracking branch 'origin/main'
Change-source: pulse-maintainer
2026-09-01 17:24:14 +01:00
rcourtman bfd53cd7bd Remove privileged helper state on full uninstall 2026-09-01 17:05:49 +01:00
pulse-triage[bot] 8d9b07905c Merge remote-tracking branch 'origin/main'
Change-source: pulse-maintainer
2026-09-01 17:04:10 +01:00
rcourtman 69a300827f Model durable collector teardown in rootless qualification 2026-09-01 16:54:50 +01:00
pulse-triage[bot] 7ead40fe5b Merge remote-tracking branch 'origin/main'
Change-source: pulse-maintainer
2026-09-01 16:54:07 +01:00
rcourtman f44968e6c6 Isolate rootless qualification cgroups 2026-09-01 16:37:11 +01:00
pulse-triage[bot] c1c0e5c6f1 Merge remote-tracking branch 'origin/main'
Change-source: pulse-maintainer
2026-09-01 16:31:15 +01:00
rcourtman 5e471c1dd5 Use supported rootless Docker qualification networking 2026-09-01 16:25:18 +01:00
rcourtman 63d1aca4c0 Fix rootless qualification packet staging 2026-09-01 16:15:21 +01:00
pulse-triage[bot] 9a5af6ff79 Keep Windows agent update signatures addressable
Change-source: pulse-maintainer
2026-09-01 16:11:04 +01:00
rcourtman d0b8b43a36 Redact customer email from billing-replay verification record
This repo is public and the 2026-03-13 hosted-signup billing-replay
record carried a real customer's email address since 2026-03-27. The
adjacent tenant, account, and Stripe ids already identify the tenant
for operators, so the address line adds nothing but exposure.

The address remains in git history; rewriting public history is a
separate decision and is not part of this commit.
2026-09-01 16:06:49 +01:00
rcourtman 53267e149d Harden least-privilege installer lifecycle state 2026-09-01 15:53:02 +01:00
rcourtman 11aadbfb1b Add rootless runtime qualification harness 2026-09-01 12:29:44 +01:00