Skip to content

Patch scan tombstones (device_patches.status='missing') accumulate unbounded #1004

@ToddHebebrand

Description

@ToddHebebrand

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:

  1. 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.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions