mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 05:29:45 +00:00
d167d7f492
## Why The Patrol weekly digest's **Investigated** line, on the Patrol page and in the weekly email, was unreliable: - It counted `investigation_outcome` lifecycle rows and read their outcome from `metadata["outcome"]`. The only writer of those rows (`updateInvestigationOutcomeLocked`) writes `prev_outcome`/`next_outcome`, so every counted row was labelled `unknown`. - Those rows are later steps of an earlier run's fix (approved, run, verified), not investigations. Meanwhile the `investigation_updated` rows every run writes were ignored, apart from one `LastInvestigatedAt` fallback count per finding. - So two runs on one finding counted once, a fix approved after last week's run counted as this week's investigation, and the email could print "Investigated: 3" followed by "No issues needed a closer look." ## What changes - `digestInvestigationRuns` counts one investigation per run that finished in the window, from the rows the findings store already writes for every run. That includes a run whose own fix was verified before it closed, and a run whose status a regression cleared mid-run. A fix that moves on afterwards relabels its run instead of counting again. - A run that failed with no outcome is labelled `failed`, the bucket name telemetry already uses. It sits after `timed_out` in `PatrolDigestOutcomeOrder`. - The summary-field fallback is removed. Every run since the v6 release writes lifecycle rows, and the fallback could count one run in two adjacent weeks. - Email: `failed` reads "ended with an error". When the count is above zero but no outcome in the breakdown has wording, the line shows the count alone instead of "No issues needed a closer look." or the watch-only sentence. - `internal/ai/findings.go` is unchanged; the digest reads the rows the store writes today. Known limits: lifecycle rows carry no session ID, so a late end written by an older run is read as the end of whichever run is open. That's the store-side stale-write problem, which is being closed separately with session checks. A run whose rows the 50-row lifecycle has already dropped is not counted. ## Proof - `internal/ai/patrol_digest_test.go` drives the store's own writers: the orchestrator's start and end updates, `UpdateInvestigationOutcome`, `ReconcileInvestigationOutcome`, `RecoverStuckInvestigation`, and `Resolve` + `Add` for a mid-run regression. The scenario covers ten finished runs plus one still running. The old code reports 8 investigations, 3 of them `unknown`. - `internal/api/contract_test.go` follows the scheduled email's path: store writers → `AISettingsHandler.BuildPatrolDigest` → `renderPatrolDigestEmail`. The old code reports two runs as `1 unknown`. - `internal/api/patrol_digest_email_test.go` checks the `failed` wording and the non-zero-count guard. - On pulse-dev, `go vet` passes for `./internal/ai/` and `./internal/api/`, and the full `./internal/ai/` package and every `PatrolDigest` test in `./internal/api/` pass. The new tests fail against the previous digest and email (negative controls via `-overlay`). ## Related The Patrol page's This week card gets the same `failed` wording and the same non-zero-count guard in #3132. Until that lands, a week with only failed runs reads "No issues needed a closer look." on the card. That's what those runs already showed as `unknown`, so it isn't a regression.