Problem/Motivation

Site Doctor's positioning is "site diagnosis over time" — but as of 1.0.0-alpha1, every run is point-in-time: checks report what is wrong now and the result is discarded. Nothing can answer when a finding first appeared, whether it was resolved, or which way the trend is going — the questions that matter most on a mature site (drift arriving in deployment waves vs. accumulating gradually, warning counts rising vs. burning down).

Findings already carry deterministic fingerprints (introduced in alpha1 for exactly this purpose): a hash of the finding's identity — check, finding type, subject — never its state. The same real-world problem produces the same fingerprint run after run. This issue adds the persistence layer that turns those fingerprints into longitudinal memory.

Steps to reproduce

Run site-doctor:check twice with a config change (or any fix) in between; nothing reports what appeared, persisted, or was resolved between the two runs, and there is no admin UI showing check results at all.

Proposed resolution

Lifecycle findings model

Findings are stored as one row per distinct fingerprint with first_seen / last_seen / resolved markers — not repeated per run. A finding that persists unchanged for a year costs one row and zero writes; the tables grow with churn (the signal), not with time × findings. A severity change closes the row and opens a new one, preserving worsened/improved history. Resolution is detected by set-difference against the current run's fingerprints, computed only for checks that completed in that run — a check that did not run (disabled, crashed, filtered) never fabricates a resolution.

Storage

Four tables via the schema API (dropped on uninstall), all access through two services — ResultsStore (write) and TrendRepository (read):

  • site_doctor_run — one row per run (window, trigger, complete/partial status).
  • site_doctor_run_check — per-check outcome per run, so "ran clean" is distinguishable from "did not run".
  • site_doctor_run_count — per-run counts by check / finding type / severity; small enough to keep long-term, this feeds aggregate trends beyond the finding purge horizon.
  • site_doctor_finding — the lifecycle rows.

Scheduled collection

Cron runs all enabled cheap checks on a configurable interval (default daily) and publishes to the store. CLI runs persist too (opt out with --no-store). The run structure accommodates future queued/chunked expensive checks without schema change.

Dashboard

New report at /admin/reports/site-doctor behind a new restrict-access-flagged view site doctor reports permission: current status by category, run-to-run churn (appeared / persisted / resolved), and trend views. Every trend view states its retention horizon. The dashboard only reads stored results — it never triggers checks — and is render-cached, invalidated when a run publishes.

Retention and knobs

All in site_doctor.settings with config schema: active findings kept indefinitely; resolved finding detail purged after 90 days (configurable); run summaries kept 365 days (configurable); disabled_checks (reported in output as disabled, never silently); persist_info_findings (default true — noise-averse sites keep Info checks running without storing lifecycle rows).

TrendRepository is the single projection source for all current and future surfaces. CLI trend output, Tool API exposure, and a thin read-only JSON endpoint for external multi-site dashboards are planned for later milestones and deliberately out of scope here.

Recipe: this lands as phased MRs against this issue; services in src/, kernel tests in tests/src/Kernel/, lifecycle transitions covered by a fixture matrix in both directions. See CONTRIBUTING.md.

Remaining tasks

  • MR A: schema + ResultsStore write path + lifecycle-transition kernel test matrix (appear / persist / resolve / severity change / reopen / partial run / crashed check / disabled check).
  • MR B: cron scheduling + disabled_checks + persist_info_findings + settings schema + --no-store.
  • MR C: TrendRepository + dashboard + permission + cache tag.
  • MR D: retention purge (chunked deletes on cron) + horizon printing + documentation.

User interface changes

New administrative report at /admin/reports/site-doctor (status, churn, and trend views). No changes to existing output surfaces beyond the CLI gaining --no-store.

API changes

New ResultsStore and TrendRepository services; new permission view site doctor reports; new settings keys (disabled_checks, persist_info_findings, collection.interval, retention.*). The result SCHEMA_VERSION may bump during alpha per the documented stability policy; fingerprint identity rules do not change.

Data model changes

Four new database tables (site_doctor_run, site_doctor_run_check, site_doctor_run_count, site_doctor_finding), created by the module and removed on uninstall. The module continues to write only to its own reporting tables.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

amangrover90 created an issue. See original summary.

  • amangrover90 committed da27acaf on 1.x
    Resolves #3608513: Results store schema and lifecycle write path
    

  • amangrover90 committed 90fef9f1 on 1.x
    Resolves #3608513: Scheduled collection, settings knobs, --no-store
    

  • amangrover90 committed 1a8237fd on 1.x
    Resolves #3608513: Trend read path, dashboard, staleness surfacing
    

  • amangrover90 committed 22f6fef7 on 1.x
    Resolves #3608513: Retention purge, horizon knobs, README store docs
    
amangrover90’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

amangrover90’s picture

Assigned: amangrover90 » Unassigned

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.