Make weekly Patrol summary help usable and evidence-aware

Replace the old build and commercial narrative with setup, recipient safety, schedule checks and honest retained-data limits in the existing Intelligence guide. Keep the old repository guide as a pointer and verify the shipped Markdown with meaningful actual-parent controls.

Contract-Neutral: Existing-surface help and rendered-documentation verification only; no runtime, route, permission or interface change.
Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-07 15:27:36 +01:00
parent 880d4f1515
commit 04493c7490
4 changed files with 326 additions and 103 deletions
+116
View File
@@ -494,6 +494,122 @@ When control level is **Controlled**, write actions pause for approval:
---
## Weekly summary and email
Pulse's **This week** summary adds up retained Patrol activity over the last
seven days. Open **Patrol → Activity** to read it; use **Settings → Reporting**
to schedule the same summary by email. The card, weekly email and **Report type**
selector are included in **v6.5.0**. An older build can lack these controls.
This is a summary of recorded activity, not a complete audit or proof that every
resource is healthy. Use individual findings, run history and **Actions** for
the result of a particular investigation or fix.
### Read the summary
The card normally says **Last 7 days**: a rolling window, not a calendar week.
If it says **Since … (older runs are no longer kept)**, its run history covers
only part of that window. Refreshing the card reads existing records; it does
not run Patrol or recover missing history.
| Line | What it means and what to check |
| --- | --- |
| **Patrol runs** | Retained runs, checks and failures in the window. The resource count is the largest number checked in one run, not a count of distinct resources across the week. Read run history for failed or missing checks. |
| **New issues** | New-finding totals recorded by those runs. The open severity counts concern findings detected within the window; older open findings are not included. Check the full findings list before concluding that nothing needs attention. |
| **Issues resolved** | Recorded automatic resolutions plus manual resolutions still retained. Dismissed and muted findings are described separately. A cleared finding does not establish that an action ran or was independently verified. |
| **Investigated** | Recorded investigation outcomes on retained findings. “Fix run, result unknown” is not a verified fix; open the finding for its result and remaining work. |
| **Fixes run** | Actions recorded as successfully executed. “Verified afterwards” is a separate count; review failed actions and waiting approvals in **Actions**. Do not run an action again merely to increase the count. |
| **Estimated spend** | Recorded Patrol model calls and their estimated USD cost, not an invoice or all Assistant usage. If some calls have no known price, the displayed amount is incomplete, not a zero-cost result. Check [Cost Tracking](AI.md#cost-tracking). |
The displayed mode is Patrol's **current** mode, not a history of mode changes.
Watch-only mode can explain why investigations or fixes were not performed,
but changing mode is not a repair for an empty summary.
### Schedule the weekly email
Scheduling requires the **advanced reporting** entitlement and an account
allowed to manage report schedules. Reading the Patrol card does not itself
grant scheduling access. See [Plans and entitlements](PULSE_PRO.md).
1. Check that email notifications are **enabled and configured** under
**Alerts → Notifications**. Listing recipients on a schedule does not
configure a mail provider or enable email.
2. Open **Settings → Reporting**, choose **Create schedule**, then set
**Report type** to **Patrol weekly summary**.
3. Enter a schedule name, weekday, time and an IANA **Timezone**, such as
`Europe/London`. The summary is weekly, covers the last seven days, and is
sent as HTML and plain text email. It is not a PDF/CSV attachment, a
resource/tag-filtered report, or a saved disk copy.
4. Set **Email recipients** deliberately. Blank uses the existing email
notification recipients; if none are configured, delivery can fall back
to the sender address. Use explicit authorised recipients to avoid that
ambiguity. The email can contain resource names, findings and costs.
5. Save with **Enabled** selected for automatic runs. **Run now** sends a real
email; it is not a preview or a test of future scheduled delivery. Use it
only when those recipients should receive the summary.
The Report type choice appears when the session reports AI as enabled. An
existing summary keeps its type when edited after AI is switched off. If the
choice is missing, check the installed version, current workspace, AI settings
and reporting access; do not change Patrol's autonomy mode, re-enrol agents or
create an API token just to reveal it.
### Check a missing email or empty summary
- **No recorded runs:** check Patrol's enabled state, configured provider,
ordinary run history and errors. “Patrol has not run” can also reflect an
unavailable Patrol service, not proof that the environment had no problems.
Do not force a run, restart Pulse or change retention just to fill the card.
- **Failed schedule:** read **Last run** and the saved error in Reporting. On
a narrow screen, use the same signed-in page at a wider viewport to see
that column. A missing Patrol service and missing email configuration are
different failures. Preserve the original error; do not repeatedly use
Run now or send synthetic alerts to diagnose them.
- **Run marked OK but no email received:** that status means Pulse's sender
returned success, not that the message reached the recipient's inbox.
Check the selected recipients and the mail provider's delivery or rejection
record, then the mailbox's filtering. A successful alert Test does not prove
that this schedule ran or used the intended recipients.
- **Email links missing:** links depend on the configured Pulse public URL.
Open Patrol in your existing authenticated browser; do not make Pulse
publicly accessible or disable TLS to make an email link work.
Keep full mail errors, recipient addresses, credentials and exports private.
Share only a locally reviewed, redacted error and the relevant schedule time
and outcome; see [Getting Help](TROUBLESHOOTING.md#-getting-help).
### Honest limits of the data
- Run history retains at most **100 runs**. Event-triggered activity can
exhaust it before seven days. The partial-window notice concerns run
retention only; its absence does not certify all other lines as complete.
- Resolved findings can be purged after **24 hours**, and dismissed findings
after **30 days**. Manual resolutions, dismissals and investigation outcomes
on purged findings can disappear from these totals. Automatic resolution
totals come from retained runs, so the lines need not reconcile exactly.
- Actions have their own retained audit records, but the digest reads a bounded
set and can show an empty action line when that source is unavailable.
Check **Actions** itself; zero in this summary is not proof that no action
took place. Pending approvals can include actions created before the window.
- Model spend depends on recorded usage and known prices. This summary cannot
establish that caching, a budget limit or a provider's bill is correct.
### API
For an existing authenticated integration, `GET /api/ai/patrol/digest` requires
`ai:execute` scope. Optional `days` is an integer from **1 to 30**, default
**7**; invalid values return 400. Scheduled email always uses seven days.
The response groups `window`, `mode`, `runs`, `findings`, `investigations`,
`actions`, `alerts` and `spend`. `window.history_complete` and
`window.history_since` describe the run-retention limitation above, not a
whole-report completeness guarantee.
A zero response can be returned when the Patrol service is unavailable.
Authentication, scope or transport errors are not a zero result. Prefer the
signed-in card for a one-off check; never extract a session cookie or put an
API token in a command, URL or public report. See [API authentication](API.md#-authentication)
for the private header-file approach when an integration needs it.
## Configuration
Configure providers in the UI: **Settings → Pulse Intelligence → Provider & Models**
+4 -103
View File
@@ -1,105 +1,6 @@
# Patrol Weekly Digest
Status: building — endpoint and in-app "This week" card on `main` (PRs #1856
and #1860, 2026-09-02); the weekly email is the `patrol_digest` report
schedule kind, and Settings > Reporting creates it through the "Report type"
choice on the schedule form (2026-10-02). 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_complete` and `window.history_since`; the card says
"since <date>" 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.EstimateUSD` over 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 is a
report schedule kind, `patrol_digest`, on the report schedule API
(`POST /api/admin/reports/schedules` with `"kind": "patrol_digest"`): the
existing scheduler, cadence, recipients, and Pro reporting entitlement are
reused, and the run renders the same digest as plain-language HTML and text
(`internal/api/patrol_digest_email.go`) through the tenant email config via
`SendEmailWithRetry`. Digest schedules are weekly and email-only; nothing is
written to disk. A run without an email destination fails with a message that
names the missing setting instead of silently doing nothing. Webhook and
Apprise channels are out of scope.
In Settings > Reporting, "Create schedule" offers a "Report type" choice
between the performance report and the Patrol weekly summary. The choice only
appears while the session reports that Patrol can run (AI on with a provider
configured, the same capability that shows the Patrol navigation), and an
existing summary schedule keeps showing its type so it can be edited after AI
is switched off. Choosing the summary reduces the form to a name, weekday,
time, timezone, and optional recipients: cadence is pinned to weekly, delivery
to email, and the format, resources, tag filter, retention, attachment, and
save-copy controls are hidden because the server fixes those values. The
schedules table describes a summary's scope as "Patrol activity, last 7 days".
## 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.
For setup, interpretation, email checks and retained-data limits, see
[Weekly summary and email](AI.md#weekly-summary-and-email) in the Pulse
Intelligence guide. The summary uses existing Patrol activity; it is not a
complete audit or proof that every resource is healthy.
+116
View File
@@ -494,6 +494,122 @@ When control level is **Controlled**, write actions pause for approval:
---
## Weekly summary and email
Pulse's **This week** summary adds up retained Patrol activity over the last
seven days. Open **Patrol → Activity** to read it; use **Settings → Reporting**
to schedule the same summary by email. The card, weekly email and **Report type**
selector are included in **v6.5.0**. An older build can lack these controls.
This is a summary of recorded activity, not a complete audit or proof that every
resource is healthy. Use individual findings, run history and **Actions** for
the result of a particular investigation or fix.
### Read the summary
The card normally says **Last 7 days**: a rolling window, not a calendar week.
If it says **Since … (older runs are no longer kept)**, its run history covers
only part of that window. Refreshing the card reads existing records; it does
not run Patrol or recover missing history.
| Line | What it means and what to check |
| --- | --- |
| **Patrol runs** | Retained runs, checks and failures in the window. The resource count is the largest number checked in one run, not a count of distinct resources across the week. Read run history for failed or missing checks. |
| **New issues** | New-finding totals recorded by those runs. The open severity counts concern findings detected within the window; older open findings are not included. Check the full findings list before concluding that nothing needs attention. |
| **Issues resolved** | Recorded automatic resolutions plus manual resolutions still retained. Dismissed and muted findings are described separately. A cleared finding does not establish that an action ran or was independently verified. |
| **Investigated** | Recorded investigation outcomes on retained findings. “Fix run, result unknown” is not a verified fix; open the finding for its result and remaining work. |
| **Fixes run** | Actions recorded as successfully executed. “Verified afterwards” is a separate count; review failed actions and waiting approvals in **Actions**. Do not run an action again merely to increase the count. |
| **Estimated spend** | Recorded Patrol model calls and their estimated USD cost, not an invoice or all Assistant usage. If some calls have no known price, the displayed amount is incomplete, not a zero-cost result. Check [Cost Tracking](AI.md#cost-tracking). |
The displayed mode is Patrol's **current** mode, not a history of mode changes.
Watch-only mode can explain why investigations or fixes were not performed,
but changing mode is not a repair for an empty summary.
### Schedule the weekly email
Scheduling requires the **advanced reporting** entitlement and an account
allowed to manage report schedules. Reading the Patrol card does not itself
grant scheduling access. See [Plans and entitlements](PULSE_PRO.md).
1. Check that email notifications are **enabled and configured** under
**Alerts → Notifications**. Listing recipients on a schedule does not
configure a mail provider or enable email.
2. Open **Settings → Reporting**, choose **Create schedule**, then set
**Report type** to **Patrol weekly summary**.
3. Enter a schedule name, weekday, time and an IANA **Timezone**, such as
`Europe/London`. The summary is weekly, covers the last seven days, and is
sent as HTML and plain text email. It is not a PDF/CSV attachment, a
resource/tag-filtered report, or a saved disk copy.
4. Set **Email recipients** deliberately. Blank uses the existing email
notification recipients; if none are configured, delivery can fall back
to the sender address. Use explicit authorised recipients to avoid that
ambiguity. The email can contain resource names, findings and costs.
5. Save with **Enabled** selected for automatic runs. **Run now** sends a real
email; it is not a preview or a test of future scheduled delivery. Use it
only when those recipients should receive the summary.
The Report type choice appears when the session reports AI as enabled. An
existing summary keeps its type when edited after AI is switched off. If the
choice is missing, check the installed version, current workspace, AI settings
and reporting access; do not change Patrol's autonomy mode, re-enrol agents or
create an API token just to reveal it.
### Check a missing email or empty summary
- **No recorded runs:** check Patrol's enabled state, configured provider,
ordinary run history and errors. “Patrol has not run” can also reflect an
unavailable Patrol service, not proof that the environment had no problems.
Do not force a run, restart Pulse or change retention just to fill the card.
- **Failed schedule:** read **Last run** and the saved error in Reporting. On
a narrow screen, use the same signed-in page at a wider viewport to see
that column. A missing Patrol service and missing email configuration are
different failures. Preserve the original error; do not repeatedly use
Run now or send synthetic alerts to diagnose them.
- **Run marked OK but no email received:** that status means Pulse's sender
returned success, not that the message reached the recipient's inbox.
Check the selected recipients and the mail provider's delivery or rejection
record, then the mailbox's filtering. A successful alert Test does not prove
that this schedule ran or used the intended recipients.
- **Email links missing:** links depend on the configured Pulse public URL.
Open Patrol in your existing authenticated browser; do not make Pulse
publicly accessible or disable TLS to make an email link work.
Keep full mail errors, recipient addresses, credentials and exports private.
Share only a locally reviewed, redacted error and the relevant schedule time
and outcome; see [Getting Help](TROUBLESHOOTING.md#-getting-help).
### Honest limits of the data
- Run history retains at most **100 runs**. Event-triggered activity can
exhaust it before seven days. The partial-window notice concerns run
retention only; its absence does not certify all other lines as complete.
- Resolved findings can be purged after **24 hours**, and dismissed findings
after **30 days**. Manual resolutions, dismissals and investigation outcomes
on purged findings can disappear from these totals. Automatic resolution
totals come from retained runs, so the lines need not reconcile exactly.
- Actions have their own retained audit records, but the digest reads a bounded
set and can show an empty action line when that source is unavailable.
Check **Actions** itself; zero in this summary is not proof that no action
took place. Pending approvals can include actions created before the window.
- Model spend depends on recorded usage and known prices. This summary cannot
establish that caching, a budget limit or a provider's bill is correct.
### API
For an existing authenticated integration, `GET /api/ai/patrol/digest` requires
`ai:execute` scope. Optional `days` is an integer from **1 to 30**, default
**7**; invalid values return 400. Scheduled email always uses seven days.
The response groups `window`, `mode`, `runs`, `findings`, `investigations`,
`actions`, `alerts` and `spend`. `window.history_complete` and
`window.history_since` describe the run-retention limitation above, not a
whole-report completeness guarantee.
A zero response can be returned when the Patrol service is unavailable.
Authentication, scope or transport errors are not a zero result. Prefer the
signed-in card for a one-off check; never extract a session cookie or put an
API token in a command, URL or public report. See [API authentication](API.md#-authentication)
for the private header-file approach when an integration needs it.
## Configuration
Configure providers in the UI: **Settings → Pulse Intelligence → Provider & Models**
@@ -0,0 +1,90 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { renderDocMarkdown } from '@/features/docs/docMarkdown';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../..');
const read = (relative: string) => readFileSync(path.join(root, relative), 'utf8');
const markdown = read('docs/AI.md');
const article = document.createElement('article');
const rendered = document.createElement('article');
rendered.innerHTML = renderDocMarkdown(markdown, 'AI');
for (
let element = rendered.querySelector('#weekly-summary-and-email');
element;
element = element.nextElementSibling
) {
if (element.tagName === 'H2' && element.id !== 'weekly-summary-and-email') break;
article.append(element.cloneNode(true));
}
const text = article.textContent!.replace(/\s+/g, ' ');
// Exercise the actual shipped Markdown, not a second copy of the advice.
describe('Patrol weekly summary help', () => {
it('ships operator help instead of a private build or sales plan', () => {
expect(read('frontend-modern/public/docs/AI.md')).toBe(markdown);
expect(text).toContain('included in v6.5.0');
expect(text).not.toMatch(/Status: building|past due|stops paying|FEATURE_REQUESTS/);
expect(read('docs/PATROL_WEEKLY_DIGEST.md')).toContain('(AI.md#weekly-summary-and-email)');
expect(article.querySelector('#read-the-summary')).not.toBeNull();
expect(article.querySelector('#check-a-missing-email-or-empty-summary')).not.toBeNull();
});
it('makes enabled mail and reporting access prerequisites of the setup flow', () => {
const steps = Array.from(article.querySelectorAll('ol li'), (item) =>
item.textContent!.replace(/\s+/g, ' '),
);
expect(steps).toHaveLength(5);
expect(text).toContain('advanced reporting entitlement');
expect(steps[0]).toContain(
'Listing recipients on a schedule does not configure a mail provider or enable email',
);
expect(steps[1]).toContain('Report type to Patrol weekly summary');
expect(steps[2]).toContain('Timezone');
expect(steps[2]).toContain('not a PDF/CSV attachment');
});
it('warns about recipient fallback and real Run now delivery', () => {
expect(text).toContain('delivery can fall back to the sender address');
expect(text).toContain('Run now sends a real email; it is not a preview');
expect(text).toContain("Pulse's sender returned success");
});
it('does not turn run or action counters into whole-estate acceptance', () => {
expect(text).toContain('largest number checked in one run, not a count of distinct resources');
expect(text).toContain('older open findings are not included');
expect(text).toContain('current mode, not a history of mode changes');
expect(text).toContain('zero in this summary is not proof that no action took place');
expect(text).toContain('Pending approvals can include actions created before the window');
});
it('separates missing inputs, partial history, unknown spend and inbox receipt', () => {
expect(text).toContain('partial-window notice concerns run retention only');
expect(text).toContain('displayed amount is incomplete, not a zero-cost result');
expect(text).toContain('not that the message reached the recipient');
expect(text).toContain('unavailable Patrol service, not proof');
expect(text).toContain(
'Manual resolutions, dismissals and investigation outcomes on purged findings',
);
});
it('preserves API errors and routes private evidence to real help anchors', () => {
expect(text).toContain('ai:execute scope');
expect(text).toContain('invalid values return 400');
expect(text).toContain('Authentication, scope or transport errors are not a zero result');
expect(text).toContain('never extract a session cookie');
for (const [slug, fragment] of [
['AI', 'cost-tracking'],
['API', '-authentication'],
['TROUBLESHOOTING', '-getting-help'],
]) {
const link = article.querySelector(`a[href="/docs/${slug}#${fragment}"]`);
expect(link?.hasAttribute('data-doc-link')).toBe(true);
const target = document.createElement('article');
target.innerHTML = renderDocMarkdown(read(`docs/${slug}.md`), slug);
expect(target.querySelector(`[id="${fragment}"]`)).not.toBeNull();
}
});
});