Commit Graph

10324 Commits

Author SHA1 Message Date
Richard Courtman 47b8664f0b Classify notification failures from senders, not error prose
Terminal notification failures are 36% of resolved delivery outcomes
fleet-wide (126,337 dead-lettered against 224,692 delivered in the week
to 2026-09-03, over 6,668 clean installs), and the category breakdown
could not say why: unknown was the modal bucket at 31,218.

The class was being derived by substring-matching the Go error message.
That fails in two ways. Any failure whose text carries none of the ~50
recognised tokens falls through to unknown, which is most of what SMTP
produces: net/smtp reports the server's verdict as a reply code, and only
535 was ever matched, so a 550 relay refusal and a 451 temporary failure
both recorded as unknown. Worse, the text being matched includes the
destination's own response body, so a third party can choose the reason
code Pulse records and shows the operator - a 500 whose body contains
"rate limit" was recorded as rate_limited rather than server_error.

Senders now declare the class where they already know it, and the
classifier reads Go's own error types before it reads any prose:
*textproto.Error for SMTP reply codes, x509 and tls for certificate
failures, net.DNSError and timeouts for connectivity. HTTP status codes
set the class at the five sites that build a status error, so the
response body is preserved for the operator's audit row but can no longer
influence the classification. Prose matching remains only as the last
resort for paths that declare nothing.

SMTP 5xx is deliberately not mapped the way HTTP 5xx is: a 550 is the
destination refusing the message, not the destination breaking, so only
the transient 4xx replies count as server_error.

Registers the wider finding as a coverage gap. The 36% is concentration,
not breadth - 72 installs that delivered nothing at all in seven days
account for half of all terminal failures, and Pulse neither backs off
nor tells those operators the destination has never once succeeded.
2026-09-03 23:42:50 +01:00
rcourtman 54d58a7406 Merge pull request #1874 from rcourtman/claude/telemetry-consent-2
Make telemetry consent a setup choice and retire the payload banner
2026-09-02 19:46:12 +01:00
Richard Courtman a70c96c8a3 Make telemetry consent a setup choice and retire the payload banner
The setup wizard's telemetry card only ever offered a way out: it led
with "enabled by default", gave no reason the data exists, and told the
reader to set PULSE_TELEMETRY=false before starting a process that had
already sent its first ping two minutes after boot. The payload-update
banner paired "we now collect more" with a one-click Disable button, was
keyed to schema v2 from July and never re-triggered across fifteen later
bumps, and its text was rewritten in August so anyone who had dismissed
it never saw the new wording. Nothing on either surface said what the
data is for or what it is never used for. No GitHub issue or discussion
has ever complained about the default-on posture, so the defensive
framing was answering a question nobody asked while quietly nudging
people to opt out.

Setup now leads with what the daily summary is for (development effort
follows real use; the features and platforms the operator relies on get
priority), names concrete exclusions (hostnames, credentials, IP
addresses), states what it is never used for (not sold or shared, not
used for advertising, not linked to a Pulse account or license), and
puts a real Usage statistics toggle on the admin-account step. The
toggle defaults to on and, when switched off, is applied through the
canonical system-settings endpoint once the admin token exists, so there
is no setup-only side channel and the account is created either way.
Neither setup screen tells the reader how to turn it off; the switch is
the control. The env-var instruction moves to PRIVACY.md where a reader
can still act on it, alongside a note that the first ping fires about
two minutes after start.

The payload-update banner is retired along with its telemetryAction deep
link that changed the preference on arrival. Payload changes are now
disclosed in a dated changelog in PRIVACY.md (back-filled from schema v2
to v17 from the telemetry package's own version notes) and in release
notes; an in-app notice is reserved for a change in kind. PRIVACY.md
gains a "What it is not used for" section whose statements are facts
about the license-server path, which never joins telemetry rows to
license or customer records; the contract treats any change to that path
as a change in kind. Settings leads with what the data is for and makes
Preview payload the primary action, because the exact runtime payload is
the disclosure an operator can verify. The security-privacy,
deployment-installability, and frontend-primitives contracts record the
new rules. Telemetry and i18n proof tests pin the setup choice, the
purpose-first and never-sold wording in every locale, and the changelog
row for the current schema so a future bump cannot land undisclosed.

Demand ledger: repos/pulse-pro FEATURE_REQUESTS.md "Telemetry consent as
a real setup choice" (named bet, pulse-pro PR #40). Supersedes the
three-commit branch behind Pulse PR #1873, rebuilt on current main.
2026-09-02 19:04:54 +01:00
rcourtman ef460aa654 Merge pull request #1871 from rcourtman/claude/helm-pages-immutable-retry
Make Helm Pages convergence idempotent on immutable chart releases
2026-09-02 19:02:52 +01:00
rcourtman 38ef8224e7 Merge pull request #1870 from rcourtman/claude/release-line-qualification
Qualify release candidates against the branch that owns their version
2026-09-02 18:30:16 +01:00
Richard Courtman 6385122400 Keep the chart release existence check on gh release view
The release promotion policy test pins every chart release command to
gh release with an explicit --repo, so the existence and prerelease
reads stay on gh release view; only the asset digest, which the CLI does
not expose, comes from the REST payload.

Contract-Neutral: Helm Pages release existence check keeps the gh release view shape the policy test pins; no product or contract change
2026-09-02 17:58:44 +01:00
Richard Courtman b6e78c18ae Make Helm Pages convergence idempotent on immutable chart releases
A convergence retry re-uploaded the chart with --clobber, which deletes
the existing asset first; GitHub refuses that on an immutable release, so
every retry for v6.4.3-rc.1 failed the Helm Pages surface after the chart
had already been published correctly. Recognise the exact chart already
held by the release through its asset digest, upload only when the asset
is missing, and refuse a different chart rather than replace it.

The same step also marked every chart release a prerelease, including the
stable helm-chart-6.4.0 and 6.4.1, which the release steward reports as a
release-state incident. The flag now follows the chart version.

Contract-Neutral: Helm Pages convergence idempotent on immutable chart releases; no product or contract change
2026-09-02 17:55:04 +01:00
Richard Courtman 79d87a4a2c Qualify release candidates against the branch that owns their version
The secure-runtime qualification required the candidate commit to be an
ancestor of origin/main. Under the release train a patch candidate is cut
from release/vX.Y and is not on main at all, so v6.4.3-rc.1's
qualification failed at "Verify detached release source" before running
any check, and the steward correctly refused to treat the candidate as
stable-capable. Resolve the branch the control plane maps the version to,
fetch that, check ancestry against it, and hand the same ref to the
attestation so the receipt records the line it was verified against.

Refs #1753

Contract-Neutral: release qualification checks ancestry against the mapped release branch; no product or contract change
2026-09-02 17:52:33 +01:00
pulse-triage[bot] ac3dc2e953 Merge pull request #1862 from rcourtman/maintainer/20260902T090949Z
Improve accessibility, TrueNAS readiness, and release reliability
2026-09-02 17:01:21 +01:00
pulse-triage[bot] 111b1bd251 Merge current Pulse upstream for publication
Incorporate the metrics startup hook capture from PR #1868 while retaining
the reviewed causal cleanup and barrier-ordering coverage. This advances the
open publication proposal without rewriting any accepted commit.

Change-source: pulse-maintainer
Contract-Neutral: Integration reconciliation only; no additional public contract delta.
2026-09-02 16:09:57 +01:00
rcourtman 5715b1032e Merge pull request #1868 from rcourtman/claude/metrics-startup-flake
Capture the metrics startup hook per store to end a test flake
2026-09-02 15:21:13 +01:00
Richard Courtman 1ac94620eb Capture the metrics startup hook per store to end a test flake
TestNewStoreDefersStartupMaintenance bounded NewStore at 200ms. On a
slow CI disk that bound tripped, the test failed, and its NewStore
goroutine kept running: the maintenance worker it spawned read the
package-level startupMaintenanceHook after the next test had installed
its own closure, closed that test's started channel a second time, and
panicked the whole rest-1 shard (run 33630289317, attempt 1).

Capture the hook once in NewStore so a store can only ever call the
hook that was installed when it was built. Prove deferral by ordering
instead of wall clock: the hook parks the worker, and NewStore must
return while it is parked. A regression to inline maintenance now
blocks that receive until the package timeout instead of flaking.
Cleanup releases the worker and waits for NewStore before restoring
the hook, so a failed run cannot leak a parked store either.

Contract-Neutral: behavioral test-flake fix with no public contract delta
2026-09-02 14:32:23 +01:00
pulse-triage[bot] d076775ee6 Merge current Pulse upstream for publication
Change-source: pulse-maintainer
2026-09-02 14:28:44 +01:00
rcourtman e6a5b8184f Merge pull request #1867 from rcourtman/claude/issue-1837-max-completion-tokens-v2
Send max_completion_tokens to GPT-5 models and learn it from the API
2026-09-02 14:24:32 +01:00
pulse-triage[bot] 10550628f9 Keep E2E validation isolated from rate limits
Core E2E shards aggregate many isolated browser sessions behind one Docker
bridge address, which can exhaust the production-sized general API limiter and
hide later assertions behind HTTP 429 responses. Raise only the explicitly
enabled development harness limit and keep malformed, unsafe, or production
overrides fail-closed.

Align the offline Proxmox journey with the reviewed native-button disclosure
ownership so adjacent links are checked against the actual disclosure control.

Change-source: pulse-maintainer
Contract-Neutral: Development-only validation isolation and regression alignment; production API limits and product behavior are unchanged.
2026-09-02 14:22:23 +01:00
Richard Courtman 9a8873c7a4 Send max_completion_tokens to GPT-5 models and learn it from the API
Patrol's readiness probe against a GPT 5.6 Luna deployment on Azure AI
Foundry failed with "Unsupported parameter: 'max_tokens' is not supported
with this model. Use 'max_completion_tokens' instead." The provider only
switched fields for o1/o3/o4 models, and only on api.openai.com, so the
GPT-5 family was sent max_tokens everywhere and Azure hosts were treated
as generic OpenAI-compatible endpoints.

Treat the GPT-5 family as reasoning models on the official and Azure
OpenAI hosts. Because an Azure deployment name need not reveal the model,
also act on the API's own instruction: when a 400 names max_completion_
tokens, re-send once in that form, drop the non-default temperature those
models refuse, and remember the answer for the life of the client so later
requests, including Patrol's streaming probes, go straight out correctly.

Refs #1837

Contract-Neutral: OpenAI provider parameter selection fix for issue 1837; no wire, API, or subsystem contract change
2026-09-02 13:29:53 +01:00
pulse-triage[bot] 492b1a1667 Merge reviewed platform table accessibility repair
Retain the exact browser-verified web candidate commit and integrate native disclosure ownership across platform tables.

Change-source: pulse-maintainer
2026-09-02 13:28:10 +01:00
pulse-triage[bot] b3716e0113 Merge reviewed metrics test stabilization
Retain the exact core-runtime candidate commit and integrate its deterministic maintenance-worker ordering and cleanup coverage.

Change-source: pulse-maintainer
2026-09-02 13:28:10 +01:00
pulse-triage[bot] 0a08c01132 Merge reviewed release convergence reconciliation
Retain the exact delivery-trust candidate commit and integrate its current-control retry and scheduled reconciliation behavior on canonical main.

Change-source: pulse-maintainer
2026-09-02 13:28:10 +01:00
pulse-triage[bot] da8731fd80 Merge frozen Pulse upstream for coordination
Incorporate the origin/main state captured at the start of the candidate batch while preserving unpublished canonical integration history. Keep the newer local browser receipt where the two already-verified lines overlap.

Change-source: pulse-maintainer
2026-09-02 13:27:45 +01:00
pulse-triage[bot] d7fdafcca5 Keep platform table disclosures on native buttons
Remove duplicate keyboard and disclosure semantics from static data-table rows across the platform surfaces. Preserve whole-row pointer convenience while leaving focus, accessible naming and expanded state on each existing native disclosure button.

The populated TrueNAS browser check also exposed an invalid combobox role on inline-only search completion, so keep it as a native textbox with aria-autocomplete.

Change-source: pulse-maintainer

Contract-Neutral: Accessibility semantics and regression coverage only; no product or API boundary changed
2026-09-02 13:17:07 +01:00
pulse-triage[bot] c09f0c07f2 Stabilize metrics startup maintenance tests
The race-enabled suite can take longer than the tests' fixed sleeps while opening SQLite stores. Assert worker ordering through channels instead, and always release and join blocked maintenance workers so a failed assertion cannot contaminate the following test.

Change-source: pulse-maintainer
2026-09-02 13:14:47 +01:00
pulse-triage[bot] f74de141b4 Reconcile convergence with current controls
GitHub reruns preserve the failed workflow SHA, so a repaired convergence control cannot resolve an already committed release. A missed workflow_run event can also leave mutable aliases stranded without another attempt.

Change-source: pulse-maintainer
2026-09-02 13:11:04 +01:00
rcourtman ff1d584a42 Merge pull request #1864 from rcourtman/claude/patrol-telemetry-provider-cost-v4
Add schema v17 Patrol provider, cost, autonomy, and outcome telemetry
2026-09-02 12:48:28 +01:00
pulse-triage[bot] 7e1543b2d6 Merge current Pulse upstream for publication
Incorporate the landed Patrol digest email work that reached origin/main after publication preflight while preserving reviewed tip 3016bc72a0 unchanged in local history. The upstream and reviewed file sets do not overlap.

Change-source: pulse-maintainer
2026-09-02 12:15:41 +01:00
rcourtman 3950e54060 Merge pull request #1865 from rcourtman/claude/patrol-digest-email
Email the Patrol weekly summary as a report schedule kind
2026-09-02 12:14:21 +01:00
pulse-triage[bot] 3016bc72a0 Merge commit '6399653836ef943bcfd4569ee992f9310513d323'
# Conflicts:
#	frontend-modern/browser-verification.json

Change-source: pulse-maintainer
2026-09-02 12:05:07 +01:00
pulse-triage[bot] 16f63e4864 Merge closed-PR CI capacity reclamation
Integrate the reviewed delivery-trust candidate while preserving commits af0e8f8d39 and 6575ebd928 unchanged in history.

Change-source: pulse-maintainer
2026-09-02 11:56:53 +01:00
rcourtman 4b4db5155a Merge origin/main into claude/patrol-telemetry-provider-cost-v4
Keeps this change's browser verification receipt; main's receipt belonged to the
navigation change that landed in #1863.
2026-09-02 11:52:20 +01:00
pulse-triage[bot] 48761126bd Merge current Pulse upstream for publication
Incorporate the landed docs-mirror race fix and exact PR #1863 merge while preserving reviewed local tip 93ed81e7e9 unchanged in history.

Change-source: pulse-maintainer
2026-09-02 11:51:45 +01:00
rcourtman 2f0ae671b0 Merge pull request #1863 from rcourtman/claude/core-e2e-nav-links
Restore Core E2E on main: link navigation, Patrol controls, badge contrast
2026-09-02 11:50:11 +01:00
pulse-triage[bot] 6399653836 Fix Assistant autocomplete screen reader focus
The composer kept DOM focus in its textarea while only visually indicating the active autocomplete option. Screen reader users therefore could not identify the controlled listbox or follow mention and slash-command selection changes.

Change-source: pulse-maintainer

Contract-Neutral: Assistant autocomplete accessibility and responsive presentation only; no API or subsystem contract changed
2026-09-02 11:32:57 +01:00
rcourtman cfac267563 Merge pull request #1859 from rcourtman/claude/docs-mirror-test-gc-race
Stop background git from racing test_docs_mirror temp cleanup
2026-09-02 11:27:52 +01:00
rcourtman eecf45fafd Email the Patrol weekly summary as a report schedule kind
The customers most likely to lapse are the ones who stopped opening Pulse,
so the in-app "This week" card cannot reach them. Report schedules gain a
kind: the default resources kind is the existing PDF or CSV performance
report, and patrol_digest emails the same weekly "what Patrol did for you"
rollup the Patrol page shows, in plain language, for the whole workspace.
It reuses the existing scheduler, cadence, recipients, tenant email config,
and Pro advanced-reporting entitlement rather than adding a second
scheduler. Digest schedules are weekly and email-only; the server fixes
scope, format, and attachments, and a run with no AI service, no Patrol, or
no email destination records a last_error that names the missing piece
instead of sending an empty week. Nothing is written to disk.

The digest assembly moves from the HTTP handler into
AISettingsHandler.BuildPatrolDigest so the schedule runner and the
endpoint share one code path. docs/MSP.md documents the kind for
providers; the Settings > Reporting "Report type" selector follows in a
separate change once it has a Pro-licensed browser pass.
2026-09-02 11:15:22 +01:00
rcourtman 3420fcdee4 Add schema v17 Patrol provider, cost, autonomy, and outcome telemetry
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.
2026-09-02 11:09:25 +01:00
pulse-triage[bot] 6575ebd928 Tighten the privileged close hook boundary
Reject job-level permission expansion and unsafe checkout selection in the closed-PR capacity workflow, and report cancellation requests separately from runs that completed during the API race.

Change-source: pulse-maintainer
2026-09-02 11:05:04 +01:00
pulse-triage[bot] af0e8f8d39 Reclaim CI capacity when pull requests close
Cancel queued and running validation workflows for a closed pull request head so obsolete matrices cannot hold the hosted-runner limit and delay required checks. Keep the privileged close hook bound to reviewed default-branch code and cover reopen, branch-reuse, identity, and API-race boundaries.

Change-source: pulse-maintainer
2026-09-02 11:03:53 +01:00
pulse-triage[bot] 93ed81e7e9 Merge operator-requested Core E2E recovery
Retain the exact browser-verified accessibility fix and test realignment commits from PR #1863 without re-parenting them.\n\nChange-source: operator-request
Change-source: pulse-maintainer
2026-09-02 11:01:10 +01:00
pulse-triage[bot] d9a98784d9 Merge reviewed TrueNAS 26 transport qualification
Retain the specialist's exact reviewed commit while integrating it with the current canonical Pulse line.\n\nChange-source: pulse-maintainer
Change-source: pulse-maintainer
2026-09-02 11:01:10 +01:00
pulse-triage[bot] aa34f74131 Merge batch-start Pulse upstream
Incorporate the upstream main state fetched at coordinator batch start before integrating reviewed local work. Resolve the single rolling browser receipt to the incoming upstream proof; no product source conflicted.\n\nChange-source: pulse-maintainer

Change-source: pulse-maintainer
2026-09-02 11:00:57 +01:00
rcourtman 1d13284f6a Realign Core E2E specs with the link navigation and Patrol controls
Core E2E has been red on main since 2026-08-29 because product changes landed
without their browser specs following:

- The desktop and mobile navigation became links (08581a2bc9, 5cfa0f26b6);
  the specs still selected role=tab and tablist. helpers.ts gains
  primaryNavigation and primaryNavigationLink, and every navigation
  selector uses them or the navigation/link roles.
- The Patrol records disclosure became "Finding options and history" and
  the per-finding Manage control became "Finding options for <title>"
  (21367b655d).
- Collapsed threshold sections are now inert and aria-hidden (434f777a59).
  Sections open collapsed, so the Ceph and threshold-override specs had been
  asserting on invisible rows; they expand the section first and follow the
  toggle button into the heading.
- The APT dialog copy changed punctuation, the backups route gained /date,
  and the mobile rail no longer has separate primary and utility rails.
- The phone-width accessibility scan runs under reduced motion so it samples
  the settled update banner instead of a mid-fade frame.

Validated on the pulse-dev rig against a pulse:test image built from this
branch: 17, 18, 66, 73, 83 and journeys/01 pass on chromium; 04, 53 (nav
flow), 66, 73, 81, 83 and journeys/01 on mobile-chrome and mobile-safari.
Probation specs 78, 91 and 92 still fail on Patrol workbench content that
changed in today's landings; that is product drift outside the navigation
change and is left for its lane.

Contract-Neutral: spec realignment and test-only navigation helpers with no runtime or contract change
2026-09-02 10:53:57 +01:00
rcourtman 908afdf0c5 Keep navigation badges readable and the skip link first in tab order
The primary and mobile navigation count badges used text-amber-900, which
tailwind.config.js defines at 25% alpha as a dark-surface background token.
Once the navigation became links (08581a2bc9, 5cfa0f26b6) and lost
aria-disabled, the axe scan in the product-trust spec stopped skipping the
Patrol badge and measured it at 1.49:1. The badges now use opaque amber-800
(slate-900 on amber-400 in dark mode) and the update banner's Pre-release
label moves from orange-700 (4.38:1) to orange-800.

The skip-to-content link lived inside AppLayout, but the global banners render
before AppLayout, so whenever the update banner showed the first Tab landed on
its Dismiss button. The link now renders from the app shell ahead of the
banner block, with AppLayout keeping the #main target.

Verified on the pulse-dev Playwright rig against a pulse:test image built
from this change: axe WCAG A/AA scans of /alerts/overview, /patrol,
/settings/infrastructure and /settings/system-general at 1280x720 and of
/actions at 390x844 report no colour-contrast violation on the navigation or
banner, and Tab from the page start focuses the skip link.

Contract-Neutral: colour and DOM-order accessibility fix with no public contract change
2026-09-02 10:53:18 +01:00
pulse-triage[bot] 2ef4492972 Qualify TrueNAS 26 JSON-RPC snapshot transport
TrueNAS 26 removes the REST API, making Pulse's authenticated JSON-RPC path the compatibility boundary for snapshot collection. Pin the full transport contract to the published 26-BETA.3 version so any fallback to removed REST endpoints is caught before release.

Change-source: pulse-maintainer
2026-09-02 10:52:41 +01:00
rcourtman b5f694b216 Merge pull request #1860 from rcourtman/claude/patrol-value-digest-card-r2
Show what Patrol did this week on the Patrol page
2026-09-02 10:52:02 +01:00
pulse-triage[bot] fc0adf7073 Merge search history keyboard accessibility
Integrate reviewed web-product candidate 02d64d1478 without re-parenting it.
Change-source: pulse-maintainer
2026-09-02 10:26:09 +01:00
pulse-triage[bot] 107e556e84 Merge current Pulse upstream for publication
Incorporate the release preflight and Helm publication fixes that reached origin/main after this gate was captured while retaining the reviewed tip e39d344a64 unchanged in local history.

Change-source: pulse-maintainer
2026-09-02 10:13:00 +01:00
rcourtman 5476288a4e Merge pull request #1840 from rcourtman/claude/helm-publish-checkout-order
Fix the release preflight worker and Helm publish, and map v6.4.3 to release/v6.4
2026-09-02 10:10:19 +01:00
pulse-triage[bot] 02d64d1478 Fix search history keyboard accessibility
The recent-search popup mixed listbox semantics with interactive delete and clear controls, leaving its keyboard and screen-reader behaviour inconsistent. Use menu semantics and explicit focus management so every action remains operable and correctly announced.

Contract-Neutral: Corrects behaviour within the existing shared search-history ownership and presentation contract; no product, API, or ownership boundary changes.
Change-source: pulse-maintainer
2026-09-02 10:00:25 +01:00
pulse-triage[bot] e39d344a64 Merge frozen Pulse upstream for publication
Incorporate the upstream state captured at the start of this coordination batch without re-parenting reviewed local history.

Change-source: pulse-maintainer
2026-09-02 10:00:02 +01:00
rcourtman 123e342c6c Show what Patrol did this week on the Patrol page
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.
2026-09-02 09:57:22 +01:00