Skip to content

Client reports: approval before sending, Approve & send all, and Dec 1 audit fixes - #64

Merged
aaronreimann merged 3 commits into
mainfrom
feature/client-reports
Nov 6, 2026
Merged

aaronreimann merged 3 commits into
mainfrom
feature/client-reports

Conversation

@aaronaltman

Copy link
Copy Markdown
Collaborator

Summary

Gets client reports ready for the December 1 run. Two parts:

  1. Every report now waits for approval. The monthly run compiles and checks every report but emails nothing. Operators send them from the Reports tab with a new Approve & send all ready button (or Send selected for ticked ones). There's a switch on the Scheduling tab to let clean reports send themselves again; it's on (approval required) by default.
  2. Fixes from a bug audit of the whole client-report flow: the run, every report section, both emails and the operator screens. Every finding was checked against the code before fixing. The unfixed, lower-risk ones are listed in the tracker as chore(deps-dev): bump laravel-vite-plugin from 3.1.0 to 3.2.0 #23–Desktop nav: Operations dropdown + Modules rail group (matches mobile); fix /clients 500 #36.

Approval and bulk send

  • client_reports.require_approval setting, on by default (ScheduledReportRunner::approvalRequired()). With it on, every schedule runs like a draft: the report is compiled, checked, and left Ready to send or Needs review.
  • Reports list:
    • Approve & send all ready (N) sends every Ready to send report in the period.
    • Send selected sends the ticked reports. A ticked held report is sent too: ticking it is the approval.
    • Both send up to 25 per press through the same sender as Send (locked per report, each delivery recorded), skip outdated, replaced and already-sent reports, and stamp each schedule's last_sent_at.
  • Scheduling tab: a switch for the setting, logged as client_report_approval_changed.
  • Confirm dialogs (resources/js/confirm-modal.js) now submit through the clicked button (requestSubmit), so a button's formaction and value survive the dialog. Checked in a browser with Playwright.

Audit fixes

Reports that could have gone out wrong are now held for review:

  • A Sucuri SiteCheck malware or blocklist hit was ignored whenever a Companion scan also ran.
  • A core-file or blocklist problem earlier in the month was hidden by a later clean check.
  • A month in which no malware scan or blocklist check finished.
  • A stale vulnerability list, or a plugin whose vulnerability lookup has never succeeded (tracker chore(deps): bump actions/upload-artifact from 4 to 7 #22).
  • The uptime monitor blocked (HTTP 429) for an hour or more, or maintenance mode for 2 hours or more. Both count as up, so they showed as 100%.
  • Files-only backups. A failed or early read of the backups. A broken SpinupWP database dump hidden by the files' size (new site_backups.database_bytes).
  • A care plan switched off by Bill.com's 01:30 UTC sync on the 1st, which used to skip the month entirely. Bill.com flips are now logged (locally only).

What the client reads:

  • Updates run from a site's Updates tab showed the raw log line; they now show the plugin's name.
  • HTML entities in plugin names are decoded.
  • A speed test with no score showed as a red 0/100; it's now skipped.
  • Requests with zero visits now count as suspect traffic.
  • A short outage no longer rounds up to 100%.
  • The inbox preview text now follows the template.
  • A site added late on Nov 1 (a 25-hour day) is no longer held as a partial month.
  • An uploaded logo was a broken /storage/… link in every inbox. It's now embedded from disk, and a non-public logo URL is never linked.

Operator screens:

  • Unchecking Enable on a schedule did nothing, and saving a paused schedule switched it back on.
  • Edited recipients now reach the schedule's unsent reports.
  • A double click can no longer send twice.
  • Recipients are de-duplicated ignoring case.
  • Send on an already-sent report warns first.
  • A draft-mode Run no longer creates a second live report beside a sent one.
  • The recipient push no longer writes an old Companion snapshot back over a newer one.
  • client_reports.client_email is now text, so long recipient lists don't fail.

Report schema is now 8.

Deploy steps

php artisan migrate
php artisan clockwork:record-site-backups
php artisan clockwork:refresh-plugin-vulnerabilities
php artisan clockwork:recompile-held-client-reports --outdated
npm run build

Then clear the view cache and run php artisan queue:restart. After deploy, approval is on: nothing is emailed until someone sends it from the Reports tab.

Also needed before Dec 1: a valid AWS key for the backup relay (tracker #11). Until then every relay site is held.

Testing

  • New tests cover every fix, the approval run, both bulk sends and the switch. Tests written for the approval-off behaviour now turn approval off first.
  • Full suite: 3,514 of 3,517 pass. The 3 failures happen the same way on main in this environment: the installer tests need MySQL, and the storage-permission test runs as root.
  • Pint clean. Biome clean on the changed JS.
  • PHPStan was not run: it couldn't be installed in this environment.

Generated by Claude Code

…ec 1

Dec 1 is the first run where clean reports email clients with nobody
looking, so a report that is wrong without being flagged goes straight
out. Five reviewers went over the run path, every section, both emails
and the operator screens; each finding was checked against the code.

Holds for review (each a client email that would have been wrong or
unexplained):
- a Sucuri SiteCheck hit on a site whose malware row comes from a clean
  Companion scan (SiteCheck results were never read when Companion ran)
- a core-file or blocklist problem earlier in the month hidden by a
  later clean check
- no malware scan or blocklist check finished all month while on
- a vulnerability list older than 48h, or a plugin whose lookup has
  never succeeded (#22): the refresh now records each plugin's last
  successful lookup
- 429-blocked time of an hour+, maintenance mode of 2h+ (both count as up)
- files-only backups, a failed backup read, or a read from before the
  month ended; SpinupWP's database half is stored apart so an empty dump
  can't hide behind the files
- a care plan switched off after the month ended (Bill.com's 01:30 UTC
  sync on the 1st made the run skip the month); the sync now logs its
  flips, locally only

Client-facing fixes: Updates-tab updates showed the raw log line; plugin
names with HTML entities; a scoreless speed test as a red 0/100; zero
visits for real requests; a short outage rounded to 100%; the preheader
ignored the template; a site added late on Nov 1 (25-hour day) held as
partial; an uploaded logo was a broken /storage link in every inbox.

Operator fixes: unchecking Enable did nothing and saving a paused
schedule re-enabled it; edited recipients didn't reach held reports;
sends to one report are now locked (no double send); recipients are
de-duplicated ignoring case; Send on a sent report warns; a draft Run
no longer adds a second live report; the recipient push no longer
writes an old Companion snapshot back; client_email is text.

Report schema 8, so unsent reports are recompiled under these rules.
Tracker, feature doc and changelog updated; what wasn't fixed is listed
in the tracker as #23 onward.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7rExPQo27K9h3tzxiNMw6
… send all ready

Reports no longer reach clients by themselves. With approval on (the
default), the monthly run compiles and checks every report and emails
nothing, whatever the schedule's delivery mode: each lands as Ready to
send or Needs review.

- Reports list: "Approve & send all ready (N)" sends every Ready to send
  report of the period; "Send selected" sends the ticked ones (a ticked
  held report too: ticking it is the approval). Up to 25 per press,
  through the same ClientReportSender as Send (locked per report, each
  delivery recorded), skipping outdated, replaced and already-sent ones,
  and stamping each schedule's last_sent_at.
- Scheduling tab: a switch for client_reports.require_approval, logged
  as client_report_approval_changed. Off restores Automatic schedules
  emailing clean reports by themselves.
- Run, Send now, the schedule form and the send confirmation say what
  happens with approval on.
- Confirm dialogs submit through the clicked button (requestSubmit on
  the next tick), so a button's formaction and value survive the dialog;
  the hidden-field copy of the value no longer lingers after Back.

Tests for the approval-off behaviour now switch approval off; new tests
cover the run, both bulk sends and the switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7rExPQo27K9h3tzxiNMw6
@aaronreimann
aaronreimann merged commit 5c87572 into main Nov 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants