mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
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:
@@ -68,8 +68,8 @@ Once the cause is resolved, the next check (on the interval, or via **Recheck**)
|
||||
## Workflow
|
||||
|
||||
1. Open **Update** from the top nav strip.
|
||||
2. Skim the card grid. The badge tells you the risk at a glance: `Safe · patch` is green, `Review · minor` is amber, `Blocked · major` is red, and a digest-only rebuild on a non-semver tag shows the gray `Digest rebuild` badge.
|
||||
3. For a safe update, click **Apply now** on the card to pull and recreate the stack immediately.
|
||||
2. Skim the card grid. The badge tells you the risk at a glance: `Safe · patch` is green, `Review · minor` is amber, `Blocked · major` is red, a digest-only rebuild on a non-semver tag shows the gray `Digest rebuild` badge, `Check uncertain` appears when the last preview check was incomplete or failed, and `Newer tag · edit Compose` marks a higher tag that requires editing the Compose pin (Compose pull cannot rewrite pins).
|
||||
3. For a safe, Compose-actionable update (digest drift or local rebuild), click **Apply now** on the card to pull and recreate the stack immediately. **Apply now** stays disabled for tag-only advisories and for uncertain checks.
|
||||
4. For a major bump, review the changelog preview and the upstream release notes. **Apply now** is disabled on the readiness board for blocked cards; to apply a major bump after review, use the stack's lifecycle **Update** action (right-click the stack in the sidebar, or open the kebab menu and choose **Update**, or click **Deploy** in the stack editor).
|
||||
|
||||
On multi-service stacks, the Updates view can also apply a single service when that service has a confirmed image update. Scheduled auto-update, webhook pull, and bulk update always refresh the full stack.
|
||||
@@ -85,6 +85,8 @@ On multi-service stacks, the Updates view can also apply a single service when t
|
||||
| `Review · minor` | Amber (AlertTriangle icon) | Minor semver bump (e.g. `1.2.3` to `1.3.0`) |
|
||||
| `Blocked · major` | Red (ShieldAlert icon) | Major semver bump (e.g. `1.2.3` to `2.0.0`). **Apply now** is disabled; the card surfaces the reason "Major version jumps require human review before applying." |
|
||||
| `Digest rebuild` | Gray | Non-semver tag (e.g. `main`, `stable`) with an updated digest (tag name unchanged; content changed; see [Tags vs digests](/features/vulnerability-scanning#tags-vs-digests)) |
|
||||
| `Check uncertain` | Amber | Preview check incomplete or failed; **Apply now** stays disabled until a full successful check |
|
||||
| `Newer tag · edit Compose` | Amber | A higher tag exists than the Compose pin; edit Compose (do not use **Apply now**) |
|
||||
|
||||
A separate inline `Rebuild available` label replaces the version diff when only the digest changed (same tag, new image). The risk badge on those cards still reflects the underlying semver classification reported by the registry.
|
||||
|
||||
@@ -133,7 +135,7 @@ For each stack with a pending image update, Sencho computes a preview by:
|
||||
|
||||
1. Parsing the compose file to enumerate every service that pulls a registry image (a service declaring only `build:`, with no `image:` key, has no registry reference to check and is excluded from this preview).
|
||||
2. Calling the registry with your configured credentials to fetch the current tag list and remote digest.
|
||||
3. Picking the highest semver tag greater than the current tag (keeping the same prefix and suffix). If the highest available tag matches the current one but the remote digest has changed, the card surfaces as a `Rebuild available` update. Sencho checks both a newer semver tag and a digest change behind the same tag; see [Tags vs digests](/features/vulnerability-scanning#tags-vs-digests).
|
||||
3. Picking the highest semver tag greater than the current tag (keeping the same prefix and suffix). If a higher tag exists, the card surfaces as a tag-only advisory (`Newer tag · edit Compose`); scheduled auto-update and **Apply now** do not rewrite Compose pins. If the highest available tag matches the current one but the remote digest has changed, the card surfaces as a Compose-actionable `Rebuild available` / digest update. Sencho checks both a newer semver tag and a digest change behind the same tag; see [Tags vs digests](/features/vulnerability-scanning#tags-vs-digests).
|
||||
4. Scoring the overall stack by the most severe image bump. Any major bump marks the stack as blocked.
|
||||
5. Normalizing Docker Hub library paths so credentials and changelog lookups resolve correctly.
|
||||
|
||||
|
||||
@@ -144,8 +144,9 @@ Each row leads with a two-character status indicator that summarizes the stack's
|
||||
|
||||
Additional indicators appear to the right of the stack name:
|
||||
|
||||
- A pulsing fuchsia dot flags that an image update is available.
|
||||
- A muted alert icon replaces the dot when the update check ran but failed to reach the registry or errored.
|
||||
- A pulsing fuchsia dot flags that an image update is available and the latest check completed successfully. That signal covers Compose-actionable digest drift (same tag, new registry content) as well as a newer tag that requires editing the Compose pin.
|
||||
- A muted warning icon appears when the last check was incomplete. When an update was also detected or previously recorded, the tooltip notes that the full stack could not be verified. When no update is recorded, the tooltip notes that update status could not be fully verified. Incomplete results are not counted under **Updates**.
|
||||
- A muted alert icon appears when the update check failed (and, when an earlier update was recorded, notes that the previous status was retained).
|
||||
- A branch icon signals that the Git source's upstream branch has moved ahead of the working copy.
|
||||
|
||||
### Search and filter chips
|
||||
@@ -159,7 +160,7 @@ The search box above the list filters stacks by name. The chip row below it filt
|
||||
- **All**: every stack discovered in `COMPOSE_DIR`.
|
||||
- **Up**: stacks where every container is running.
|
||||
- **Down**: stacks with at least one stopped or exited container.
|
||||
- **Updates**: stacks with a pending image update.
|
||||
- **Updates**: stacks with a confirmed pending image update (latest check completed successfully). Incomplete or failed checks are excluded from this chip.
|
||||
|
||||
Each chip carries a live count. Click **Hide filters** in the top-right of the row to collapse the chips when you want a denser list. Search and chips combine: filtering by **Updates** and typing a few characters returns the intersection.
|
||||
|
||||
@@ -281,7 +282,13 @@ Each row maps one compose concept to the value it resolves to right now:
|
||||
|
||||
A footer card under the rows surfaces the first published port as a clickable **EXPOSED** link, so you can jump straight to the running app.
|
||||
|
||||
If an image update is available, or the stack declares one or more services with a local `build:` section, an inline banner appears below the rows. Registry updates name each image with a pending update and show its version transition (for example `27.1.4 -> 27.1.5`); the detail line below reads `patch · safe to apply` (green), `minor · review recommended` (amber), or `major · breaking changes possible` (rose), followed by an **apply** button. Build-only stacks show **Rebuild available** instead of a version bump, with a **Rebuild & Update** button. A stack that mixes registry images and local builds still shows a single banner, with the rebuild note folded into the same detail line. Major bumps use the rose styling and are worth reviewing before applying.
|
||||
If an image update is available, or the stack declares one or more services with a local `build:` section, an inline banner appears below the rows.
|
||||
|
||||
- **Digest rebuild / rebuild**: same-tag registry content changed, or a local `build:` service needs rebuilding. The banner shows the version context and an **apply** (or **Rebuild & Update**) button when the check completed successfully.
|
||||
- **Newer tag**: a higher semver tag exists than the pin in Compose. The detail line reads `newer tag · edit Compose pin`. There is no **apply** button; Compose pull does not rewrite image pins. Edit the Compose file (or use your usual pin-change workflow), then deploy.
|
||||
- **Incomplete or failed check** with no confirmed update: a separate amber banner explains that status is uncertain until a full check succeeds.
|
||||
|
||||
For actionable registry updates, the detail line also reads `patch · safe to apply` (green), `minor · review recommended` (amber), or `major · breaking changes possible` (rose). Build-only stacks show **Rebuild available** instead of a version bump, with a **Rebuild & Update** button. A stack that mixes registry images and local builds still shows a single banner, with the rebuild note folded into the same detail line. Major bumps use the rose styling and are worth reviewing before applying.
|
||||
|
||||
Rebuilds can take longer than a registry pull and depend on the local Dockerfile context, network access, and base-image availability. Atomic rollback restores compose and env files only; previously built image layers are not rolled back automatically.
|
||||
|
||||
|
||||
@@ -149,6 +149,69 @@ components:
|
||||
type: boolean
|
||||
example: true
|
||||
|
||||
UpdatePreviewImage:
|
||||
type: object
|
||||
required:
|
||||
[service, image, current_tag, next_tag, has_update, digest_update, tag_update, semver_bump, check_status]
|
||||
properties:
|
||||
service: { type: string }
|
||||
image: { type: string }
|
||||
current_tag: { type: string }
|
||||
next_tag: { type: ["string", "null"] }
|
||||
has_update: { type: boolean }
|
||||
digest_update:
|
||||
type: boolean
|
||||
description: Same-tag registry content drift; Compose pull can apply without changing the pin.
|
||||
tag_update:
|
||||
type: boolean
|
||||
description: A higher pinned semver exists; advisory until Compose is edited.
|
||||
semver_bump:
|
||||
type: string
|
||||
enum: [none, unknown, patch, minor, major]
|
||||
check_status:
|
||||
type: string
|
||||
enum: [ok, partial, failed, not_checkable]
|
||||
|
||||
UpdatePreviewSummary:
|
||||
type: object
|
||||
required:
|
||||
[has_update, primary_image, current_tag, next_tag, semver_bump, update_kind, blocked, blocked_reason, has_build_services, rebuild_available, check_status]
|
||||
properties:
|
||||
has_update: { type: boolean }
|
||||
primary_image: { type: ["string", "null"] }
|
||||
current_tag: { type: ["string", "null"] }
|
||||
next_tag: { type: ["string", "null"] }
|
||||
semver_bump:
|
||||
type: string
|
||||
enum: [none, unknown, patch, minor, major]
|
||||
update_kind:
|
||||
type: string
|
||||
enum: [tag, digest, none]
|
||||
blocked: { type: boolean }
|
||||
blocked_reason: { type: ["string", "null"] }
|
||||
has_build_services: { type: boolean }
|
||||
rebuild_available: { type: boolean }
|
||||
check_status:
|
||||
type: string
|
||||
enum: [ok, partial, failed]
|
||||
|
||||
UpdatePreview:
|
||||
type: object
|
||||
required: [stack_name, images, build_services, summary, rollback_target, changelog]
|
||||
properties:
|
||||
stack_name: { type: string }
|
||||
images:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/UpdatePreviewImage"
|
||||
build_services:
|
||||
type: array
|
||||
items: { type: string }
|
||||
summary:
|
||||
$ref: "#/components/schemas/UpdatePreviewSummary"
|
||||
rollback_target: { type: ["string", "null"] }
|
||||
changelog: { type: ["string", "null"] }
|
||||
|
||||
LabelSource:
|
||||
type: string
|
||||
description: Provenance of a label. `unknown` when a container or image could not be inspected.
|
||||
@@ -1788,6 +1851,68 @@ paths:
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/stacks/{stackName}/update-preview:
|
||||
get:
|
||||
operationId: getStackUpdatePreview
|
||||
tags: [Stacks]
|
||||
summary: Compute image update preview (read-only)
|
||||
description: |
|
||||
Computes the current registry update preview for the stack without
|
||||
mutating persisted scanner state. Use POST when the client should
|
||||
reconcile sticky update indicators after an authoritative-negative
|
||||
result. Requires `stack:read` permission.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/stackName"
|
||||
- $ref: "#/components/parameters/nodeId"
|
||||
responses:
|
||||
"200":
|
||||
description: Update preview.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpdatePreview"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
post:
|
||||
operationId: reconcileStackUpdatePreview
|
||||
tags: [Stacks]
|
||||
summary: Compute update preview and reconcile sticky state
|
||||
description: |
|
||||
Computes the same preview as GET. When every image reports
|
||||
`check_status: ok` and `has_update` is false (mixed `ok` +
|
||||
`not_checkable` does not clear), clears sticky confirmed update rows
|
||||
for the stack and sets `reconciled: true`. Requires `stack:read`
|
||||
permission.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/stackName"
|
||||
- $ref: "#/components/parameters/nodeId"
|
||||
responses:
|
||||
"200":
|
||||
description: Update preview, with reconcile outcome.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/UpdatePreview"
|
||||
- type: object
|
||||
required: [reconciled]
|
||||
properties:
|
||||
reconciled:
|
||||
type: boolean
|
||||
description: |
|
||||
True when sticky update rows were cleared after an
|
||||
authoritative-negative preview.
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
|
||||
/api/stacks/{stackName}/update-readiness:
|
||||
get:
|
||||
operationId: getStackUpdateReadiness
|
||||
|
||||
Reference in New Issue
Block a user