Files
sencho/backend/src/helpers/autoUpdateDigestGate.ts
T
Anso 719180f156 fix(fleet): verify update status before removing readiness cards (#1697)
* fix(fleet): verify update status before removing readiness cards

Full-stack Apply now rechecks persisted status after the health gate starts, reloads the live preview before dropping a card, and invalidates the hub fleet aggregation so cleared updates cannot resurrect from a stale cache.

Closes #1686

* fix(fleet): align persisted update status with preview semver detection

Share digest-plus-tag detection so post-Apply sidebar status matches Fleet and Anatomy.

* fix(fleet): keep tag-only updates advisory for Compose automation

Expose digestUpdate vs tagUpdate from checkImage so scheduled and API auto-update only apply same-tag digest drift Compose can pull.

* docs: clarify scheduled auto-update applies digest drift only

Document that higher pinned tags stay advisory until Compose is changed, matching schedule and Run Now behavior.

* docs: require Compose pin edits for higher-tag advisories

Stop recommending Apply now or Update as remedies that cannot rewrite a pinned image tag.

* docs: clarify Apply now pulls pinned tags only

Align the detection-cadence bullet with digest-rebuild vs higher-tag guidance.

* fix(fleet): keep tag advisories after apply and scheduled updates

Tag-only previews were treated as cleared on Fleet reload, and scheduled/
Run Now paths wiped status without rechecking. Align post-update verification
with the manual Apply path (health gate first, recheck, no blind clear) and
block digest apply when sibling image checks failed.

* fix(fleet): clear eslint unused-arg and containers assignment
2026-07-26 03:09:21 -04:00

76 lines
2.7 KiB
TypeScript

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 a sibling image check failed and a full-stack Compose
* update must not run (it would pull/recreate the unverified image as
* collateral). Null when digest apply may proceed.
*/
export function messageWhenDigestApplyBlockedByCheckErrors(
stackName: string,
state: Pick<AutoUpdateDigestGateState, 'hasDigestUpdate' | 'checkErrors'>,
): string | null {
if (!state.hasDigestUpdate || state.checkErrors.length === 0) return null;
return `Stack "${stackName}": WARNING - digest update available but ${state.checkErrors.length} image check(s) failed; skipped auto-update (${state.checkErrors.join('; ')}).`;
}
/** 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.`;
}