fix: reconcile sticky update indicators with Anatomy preview (#1698)

* fix: reconcile sticky update indicators with Anatomy preview

Sidebar, Updates filter, and Fleet treated retained partial/failed
scanner has_update as confirmed. Keep raw state for retention/notifications,
project confirmed-only to APIs, show distinct incomplete indicators, and
clear sticky rows only after an authoritative-negative preview.

Closes #1685

* test: align sidebar truncate E2E with failed-over-retained precedence

Purple update indicators are confirmed-only; hasUpdate with a failed
check correctly shows the failed trailing icon.

* fix: clear confirmed update rows on authoritative-negative preview

Address audit SF-1/SF-2/SF-3: observation-watermark clears for older
ok+has_update rows (DB + memory gens), Fleet checkability parity with
backend not_checkable, and Updates chip confirmed-only regressions.

* fix: tombstone equal-generation writers on preview clear

Advance the per-stack write generation when clearing at the observation
watermark so a scanner reserved before preview cannot recreate the row
after an authoritative-negative reconcile.

* fix: clear sticky updates with digest and tag preview parity

Share detection across scanner and preview, keep GET read-only with POST reconcile, gate Apply to digest and rebuild updates, and invalidate the hub fleet cache on clear.

* test: set digestUpdate on auto-update checkImage mocks

Scheduler and execute routes now gate Compose on digest drift; fixtures that expect an apply need digestUpdate so they exercise the update path.

* fix: clear unused lint errors on sticky update branch

Drop unused partial helper and fleet invalidate import; keep the CacheService inflight self-ref as let with an eslint exception so tsc stays green.

* fix: use inflight holder for CacheService prefer-const

Keep generation-aware ownership without a let self-reference that fights ESLint and tsc.
This commit is contained in:
Anso
2026-07-25 15:42:19 -04:00
committed by GitHub
parent 8b5407fcff
commit 0daddfde00
43 changed files with 2529 additions and 390 deletions
@@ -298,6 +298,26 @@ describe('CacheService', () => {
it('is a no-op for missing keys', () => {
expect(() => cache.invalidate('ns:missing')).not.toThrow();
});
it('prevents an in-flight fetch started before invalidate from committing afterward', async () => {
let resolveFetch!: (value: string) => void;
const staleFetcher = vi.fn(() => new Promise<string>((resolve) => {
resolveFetch = resolve;
}));
const stalePromise = cache.getOrFetch('fleet-updates', 60_000, staleFetcher);
cache.invalidate('fleet-updates');
const freshFetcher = vi.fn().mockResolvedValue('fresh');
const fresh = await cache.getOrFetch('fleet-updates', 60_000, freshFetcher);
expect(fresh).toBe('fresh');
expect(freshFetcher).toHaveBeenCalledTimes(1);
resolveFetch('stale');
await expect(stalePromise).resolves.toBe('stale');
// Stale writer must not overwrite the post-invalidate entry.
expect(cache.get<string>('fleet-updates')).toBe('fresh');
});
});
describe('invalidateNamespace', () => {