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.
5.9 KiB
Patrol Weekly Digest
Status: building — first slice (aggregation endpoint and in-app "This week"
card) landing on main 2026-09-01; weekly email is the unscheduled second
slice. Demand ledger: pulse-pro/FEATURE_REQUESTS.md, "Patrol weekly digest
(what Patrol did for you)", a named bet under the Patrol operations loop.
The job, in the customer's words
"Show me what I am paying for." A Pro customer turns Patrol on, it runs about five times a day, and nothing in Pulse ever adds that up. They see individual findings when they open the page and individual emails when a finding fires, but never the week: how often Patrol looked, what it caught, what it fixed, what it cost. The 2026-09-01 assessment found 33 of 175 Pro subscriptions past due and 80 percent of paying installs never seeing the paid loop fire. A customer who cannot see the work stops paying for it.
The least-expert plausible reader is a homelab operator who set Patrol up once and opens Pulse when an alert email arrives. They do not know what an "investigation", a "verdict", or an "evidence class" is. The digest must read as a plain weekly account, and every number must lead to something they can click or a decision they can make.
What the digest reports
Everything below is computed from data Pulse already records. No new telemetry fields; the readout task grades this bet from support and issue mentions.
| Line | Source | What the reader does with it |
|---|---|---|
| Runs: how many times Patrol checked, how many checks in total, how many resources it covered, when it last ran | PatrolRunRecord history (internal/ai/patrol.go, capped at 100 runs) |
Confirms Patrol is alive. If runs are zero or failed, the card says so and points at Patrol setup. |
| Mode: monitor, approval, assisted, or full | Service.GetEffectivePatrolAutonomyLevel |
Explains why actions were or were not taken. Monitor mode with fixable findings is the upgrade prompt, stated plainly. |
| New issues this week, and how many are still open by severity | run new_findings totals plus the findings store for still-tracked findings detected in the window |
Open critical or warning issues are the thing to look at; the count links to the findings list. |
| Issues resolved and how many Patrol resolved on its own | run resolved_findings totals plus manual resolutions still in the findings store |
This is the "it did something" line. |
| Issues you dismissed or muted | finding lifecycle events (dismissed, suppressed) in the window |
Reminds the reader what they told Patrol to ignore. |
| Investigations and their outcomes | finding lifecycle investigation_outcome events in the window |
Shows the paid loop firing; outcomes that need attention are named. |
| Actions proposed, approved, executed, verified, failed, and still waiting | canonical action audit records with origin.surface = patrol (internal/unifiedresources) |
"Waiting" links straight to Actions and approvals. |
| Alerts Patrol looked into | alert-triggered runs (alert_fired, alert_flapping) and their distinct alert identifiers |
Shows Patrol responding to the reader's own alerts, not just its schedule. |
| Estimated model spend, tokens, and calls | internal/ai/cost usage events with use_case = patrol |
Answers "what does this cost me" without opening the AI cost dashboard. Marked as an estimate; unknown pricing is stated, never zeroed. |
Deliberately left out: per-run tool call traces, evidence classes, verdict strings, model names, and anything the reader cannot act on. Those stay in run history and the Actions audit, one click away.
Honest limits of the data
- Run history keeps the last 100 runs. At five or six runs a day a week fits,
but a busy install with event-triggered runs can overflow. The payload
carries
window.history_completeandwindow.history_since; the card says "since " instead of "this week" when the window is cut short. - Resolved findings are purged from the findings store 24 hours after resolution, and dismissed findings after 30 days. Resolution totals therefore come from run records, and severity is only reported for findings still tracked. Investigation outcomes are read from lifecycle events on tracked findings, so a finding that was investigated, fixed, and purged more than a day ago no longer contributes. Executed and verified actions do not have this gap: action audit records are durable.
- Spend is
cost.EstimateUSDover recorded usage events. When the model has no known price the digest says pricing is unknown rather than reporting a smaller number.
Surface
In-app card first. A "This week" card at the top of the Patrol page's
Activity tab, above Verified outcomes
(frontend-modern/src/features/patrol/PatrolWeeklyDigestCard.tsx, backed by
GET /api/ai/patrol/digest?days=7). It costs nothing to deliver, every paying
install can see it, and the Activity tab is already where "what happened"
questions are answered. The Inbox stays a decision surface and does not gain a
summary card.
Weekly email second. The past-due population is the population that has
stopped opening Pulse, so the email is the slice that reaches them. It should
render the same digest through the existing enhanced email manager
(internal/notifications/email_enhanced.go, SendEmailWithRetry) on a weekly
schedule, respecting the existing notification enable state and email
destination. Webhook and Apprise channels are not in scope for the first
email slice. Not scheduled; the ledger entry's decline condition governs it.
API
GET /api/ai/patrol/digest — requires ai:execute scope like the other
Patrol read endpoints. Query days (1 to 30, default 7). Returns the window,
current mode, and the run, finding, investigation, action, alert, and spend
rollups described above. Empty history returns zero counts with
window.history_complete = true; a missing AI or Patrol service returns the
same zero shape so the card can render its "Patrol has not run" state.