Skip to content

feat(synchronizer): evict unmanaged inventory rows matching discovery filters during sync#544

Open
clanker-pel[bot] wants to merge 3 commits into
mainfrom
clanker/PLA-97
Open

feat(synchronizer): evict unmanaged inventory rows matching discovery filters during sync#544
clanker-pel[bot] wants to merge 3 commits into
mainfrom
clanker/PLA-97

Conversation

@clanker-pel

@clanker-pel clanker-pel Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Discovery filters (agent.resourcePlugins[].discoveryFilters) were forward-only. Discovery applies them when scanning the cloud, so a matching resource is never added to inventory — but the Synchronizer, which polls each existing inventory row via the plugin Read and removes a row only on NotFound, never consulted the filters. A resource discovered before a filter existed (or before the filter matched its tags) therefore stayed in inventory permanently, even though the filter should now exclude it. This surfaced as ~25 bootstrap-tier unmanaged rows wedged in formae inventory.

This makes the Synchronizer respect discovery filters: what Discovery skips on the way in, the Synchronizer now evicts on the next poll, keeping a single mental model for filter semantics.

  • The Synchronizer caches each namespace's MatchFilters (already carried on PluginInfoResponse) and attaches the type-matching filters to each sync resource update, scoped strictly namespace-then-type so a filter never bleeds across namespaces that reuse a type name.
  • On a successful sync Read, the persister evicts an inventory row when the authoritative current DB row is unmanaged and its freshly-read cloud state matches any attached filter. The check runs before the "only persist if changed" comparison, so an unchanged-but-matching wedged row is still removed.
  • Eviction is a DB-only tombstone (DeleteResource) — the identical call the existing NotFound path uses. The cloud object is never touched; the row simply stops appearing in inventory, matching Discovery's "never added" semantics. A distinct log line makes filter-eviction distinguishable from NotFound eviction.

A managed row that happens to match a filter is never evicted — the guard gates on the authoritative current DB row's managed flag, so a filter change can never silently drop a resource that is under management. The reused matcher fails closed, so a match-evaluation error never triggers a delete.

clanker-pel Bot added 3 commits July 4, 2026 16:34
…lters on sync read

When the synchronizer reads a resource that already exists in inventory as an
unmanaged row, check whether the freshly-read cloud state matches any attached
discovery filter. If so, tombstone the row (DB-only delete, no cloud mutation)
instead of updating it. This closes the gap where a resource discovered before
a filter was added would remain in inventory forever.

The eviction check runs after the nil-guard (so only existing rows are
evaluated) but before the JsonEqualRaw early-return (so a wedged row with
unchanged properties is still evicted when it begins to match a filter).
Managed rows and non-matching rows fall through to the normal persist path.

The filter evaluation merges Properties and ReadOnlyProperties to get the
complete cloud state, mirroring the merge that Discovery uses. Filters with
no conditions are skipped to avoid accidentally evicting every unmanaged row.
…naged rows

Extend the synchronizer's per-namespace plugin cache to capture the
MatchFilters reported by each plugin alongside the resource schemas.
After the available resource-update list is assembled, attach the
type-matched filters to each update using the resource's own namespace
cache so no filter can bleed across namespaces.

A small unexported helper findMatchFiltersForType mirrors the identical
helper in the discovery package, selecting only the filters whose
ResourceTypes list includes the resource type being synchronized.

Adds an integration test that proves the end-to-end path: an unmanaged
inventory row whose freshly-read cloud state matches a plugin discovery
filter is evicted from the datastore after a ForceSync, while a managed
row matching the same filter is left untouched.
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.

0 participants