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
@@ -0,0 +1,62 @@
import type { ImageCheckResult } from '../services/ImageUpdateService';
/** Accumulator for auto-update image checks (digest vs tag-only vs error). */
export interface AutoUpdateDigestGateState {
hasDigestUpdate: boolean;
hasTagOnlyUpdate: boolean;
updatedImages: string[];
checkErrors: string[];
}
export function createAutoUpdateDigestGateState(): AutoUpdateDigestGateState {
return {
hasDigestUpdate: false,
hasTagOnlyUpdate: false,
updatedImages: [],
checkErrors: [],
};
}
/**
* Record one image check into the digest gate. Only digest drift is Compose-
* actionable for auto-update; tag bumps stay advisory.
*/
export function recordAutoUpdateImageCheck(
state: AutoUpdateDigestGateState,
imageRef: string,
result: ImageCheckResult,
): void {
if (result.digestUpdate) {
state.hasDigestUpdate = true;
state.updatedImages.push(imageRef);
return;
}
if (result.tagUpdate) {
state.hasTagOnlyUpdate = true;
return;
}
if (result.error || result.checkStatus === 'failed' || result.checkStatus === 'partial') {
state.checkErrors.push(result.error ?? 'Update check incomplete');
}
}
/** Operator message when no digest-actionable update was found. */
export function messageWhenNoDigestUpdate(
stackName: string,
state: Pick<AutoUpdateDigestGateState, 'hasTagOnlyUpdate' | 'checkErrors'>,
imageRefCount: number,
): string {
if (state.hasTagOnlyUpdate) {
const errNote = state.checkErrors.length > 0
? ` (${state.checkErrors.length} check(s) failed)`
: '';
return `Stack "${stackName}": newer tag available but Compose pin unchanged; skipped auto-update${errNote}.`;
}
if (state.checkErrors.length > 0 && state.checkErrors.length === imageRefCount) {
return `Stack "${stackName}": WARNING - all image checks failed (${state.checkErrors.join('; ')}). Unable to determine update status.`;
}
if (state.checkErrors.length > 0) {
return `Stack "${stackName}": all reachable images up to date (${state.checkErrors.length} check(s) failed).`;
}
return `Stack "${stackName}": all images up to date.`;
}