Context: Split out from the compliance-count fix (PR linked below). That PR stops counting 'missing' rows as outstanding patches, but does not address the underlying data-hygiene problem: the 'missing' rows themselves keep piling up.
Mechanism: Agent scan ingestion (apps/api/src/routes/agents/patches.ts:40-43) marks all of a device's existing device_patches rows status='missing' at the start of every scan, then re-inserts the rows the current scan reports as 'pending'/'installed' (keyed on (deviceId, patchId)). Any row whose patch is not in the current scan is left at 'missing' forever. For Linux package sources the externalId is linux:<pkg>:<version> (agents/patches.ts:139-141), so every package upgrade creates a new patchId and orphans the old row → tombstones grow with churn.
Observed (US prod, 2026-05-29): pressless had 822 installed + 960 missing = 1,782 device_patches rows; the 960 missing are historical (package, version) pairs no longer current. MacBook-Pro-3: 162 installed + 306 missing.
Why it matters beyond the (now-fixed) counts:
- Unbounded row growth in
device_patches per device over its lifetime.
- The
'missing' enum value is semantically a tombstone but reads like "a patch the device is missing", which is exactly what caused the compliance-count bug. The name is a foot-gun.
Options to consider:
- On scan ingest,
DELETE rows absent from the latest scan instead of marking them 'missing' (drop the tombstone concept entirely). Simplest; loses no real information since 'missing' carries none.
- Keep tombstones but prune them (e.g. delete
'missing' rows older than N days, or older than the device's last successful scan) via the ingest path or a periodic job.
- Rename the enum value to something honest (
'stale'/'absent') to prevent future "count it as missing" mistakes — coordinate with a migration since it's a pg enum.
Acceptance: scanning a device repeatedly does not grow its device_patches row count without bound; the chosen approach is covered by a test that scans twice with a changed package set and asserts the stale rows are gone (or pruned).
Single source of truth for "outstanding" is now OUTSTANDING_DEVICE_PATCH_STATUSES in apps/api/src/db/schema/patches.ts.
Context: Split out from the compliance-count fix (PR linked below). That PR stops counting
'missing'rows as outstanding patches, but does not address the underlying data-hygiene problem: the'missing'rows themselves keep piling up.Mechanism: Agent scan ingestion (
apps/api/src/routes/agents/patches.ts:40-43) marks all of a device's existingdevice_patchesrowsstatus='missing'at the start of every scan, then re-inserts the rows the current scan reports as'pending'/'installed'(keyed on(deviceId, patchId)). Any row whose patch is not in the current scan is left at'missing'forever. For Linux package sources theexternalIdislinux:<pkg>:<version>(agents/patches.ts:139-141), so every package upgrade creates a newpatchIdand orphans the old row → tombstones grow with churn.Observed (US prod, 2026-05-29):
presslesshad 822 installed + 960 missing = 1,782device_patchesrows; the 960 missing are historical (package, version) pairs no longer current.MacBook-Pro-3: 162 installed + 306 missing.Why it matters beyond the (now-fixed) counts:
device_patchesper device over its lifetime.'missing'enum value is semantically a tombstone but reads like "a patch the device is missing", which is exactly what caused the compliance-count bug. The name is a foot-gun.Options to consider:
DELETErows absent from the latest scan instead of marking them'missing'(drop the tombstone concept entirely). Simplest; loses no real information since'missing'carries none.'missing'rows older than N days, or older than the device's last successful scan) via the ingest path or a periodic job.'stale'/'absent') to prevent future "count it as missing" mistakes — coordinate with a migration since it's a pg enum.Acceptance: scanning a device repeatedly does not grow its
device_patchesrow count without bound; the chosen approach is covered by a test that scans twice with a changed package set and asserts the stale rows are gone (or pruned).Single source of truth for "outstanding" is now
OUTSTANDING_DEVICE_PATCH_STATUSESinapps/api/src/db/schema/patches.ts.