You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
chore: drop the metrics-wipe notice now the backend fix has landed
"Fix org-wide metrics wipe on image tag deletion" is done, so deleting SBOM
data no longer zero-fills org-wide Container Scanning metrics. The notice
printed above every delete confirmation is now false, so it goes, along with
the spec section and pending-table row that tracked it.
The confirmation prompt itself stays - deleting is still destructive.
Deletes also stay sequential, but the comment no longer claims the wipe as
the reason. A cleanup run happens before the upload rather than in front of a
waiting user, and one request at a time is what makes "deleted 77 of 80, here
are the 3 that failed" straightforward to report.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: SPECS/README.md
+1-2Lines changed: 1 addition & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,7 +9,6 @@ This is the single source of truth for all project tasks and specs.
9
9
| Task | Spec | Notes |
10
10
|---|---|---|
11
11
| Tag count on `ImageSummary`|[images.md](commands/images.md)|**Backend**: add a tag count to `listOrganizationImages`' response so `images` can show a Tags column without one extra request per image. Deriving it client-side was deliberately dropped from OD-710's first PR |
12
-
| Confirm the org-wide metrics wipe fix |[images.md](commands/images.md)|**Blocks merging `image --delete --keep-latest`** (built, PR 3). Every tag delete zero-fills org-wide Container Scanning metrics until the next nightly scan; a delete loop fires it once per tag. OD-710 records the fix as "mostly landed" — nobody has confirmed it shipped. Confirm, then merge and drop `METRICS_WIPE_NOTICE`|
13
12
| Resolve the per-image tag budget |[images.md](commands/images.md)| The cap is org-wide and counts image × tag rows; `--keep-latest` is per image, so the safe ceiling is `cap ÷ images`. Four options, none chosen: `_main_/projects/container-tagging-guidance/research/per-image-tag-budget.md`. The CLI warns today; it does not solve it |
14
13
15
14
## Command Inventory
@@ -97,4 +96,4 @@ This is the single source of truth for all project tasks and specs.
97
96
| 2026-09-10 | Coverage **status** surfaced in `repositories` and `repository`, from the API's new `Coverage.status` ([`CoverageStatus`](https://api.codacy.com/api/api-docs#tocs_coveragestatus): `None`/`UpToDate`/`Waiting`/`Stopped`), mirroring codacy-spa#3110. **No API bump** — pinned `57.4.17` already ships the field. It rides on `Coverage`, embedded only in `RepositoryWithAnalysis`, so these two commands are the only places it can appear; `ls`/`directories` (flat `coverageWithDecimals`) and `pull-request`/`pull-requests` (`PullRequestCoverage`/`DiffCoverage`) carry no status. The payload shapes differ in more than `status`, which is what drove the rendering: `Waiting` returns a **stale** percentage (from `lastCommitWithCoverage`, `valueUpdatedAt` older than `statusUpdatedAt`), `Stopped` returns **no percentage at all**, `None` returns nothing but the status, and `status` is `undefined` on a large share of repositories. **`repositories`:** a dim `⋯` after a `Waiting` value, a dim `⊘` *instead of* a `Stopped` value, and a legend under the table carrying only the statuses actually present (`coverageStatusLegend`). Glyphs follow the existing vocabulary — `⋯` is already `formatStandards`'s "not final yet" marker, `⊘` shares the Mathematical Operators block with the `⊙` public-repo marker — no emojis. **`repository`:** the Metrics row spells the state out (`Not reported yet for the latest commit — value from 11h ago (5474cbf)` / `Stopped receiving reports 2026-08-26 — last report 8752dbd`, plus `— coverage gate no longer enforced` when `goals.minCoveragePercentage` is set / dim `Not set up` for `None`, which a bare `N/A` could never distinguish from an uncomputed metric), colored on `formatAnalysisStatus`'s existing scale (blueBright = in-flight, yellow = attention, dim = nothing there). **Analysis row rewritten:** `formatAnalysisStatus` gained an authoritative `coverageStatus` that wins over its `expectsCoverage`/`hasCoverageData` heuristic, extracted into `coverageAnalysisSuffix`. The heuristic was *wrong* for `Waiting` — a waiting repo still reports a (stale) percentage, so `hasCoverageData` was true and the row said nothing in exactly the case worth surfacing — and vague for `Stopped` ("Missing coverage reports"). That let `repository` **drop its `listCoverageReports` call entirely** (5 parallel requests → 4), which in turn means repository-token users get the coverage state for the first time (`getRepositoryWithAnalysis` is whitelisted, `listCoverageReports` is not) and `unavailable` is now `["pullRequests"]` alone. `pull-request` keeps the heuristic — its coverage models have no status field — and is untouched. Accepted trade-off: with `status` undefined, `repository`'s Analysis row now shows no coverage hint where the heuristic might have said "Missing coverage reports". Also de-duplicated `repositories.ts`'s local `formatMetric` (a stale copy of the shared `colorMetric` returning a bare `"N/A"`), which would otherwise have let coverage and complexity/duplication drift inside the same table. Five new helpers in `utils/formatting.ts` (`coverageStatusGlyph`/`formatRepoCoverageCell`/`coverageStatusLegend`/`coverageStatusNote`/`formatRepoCoverageDetail`); `formatCoverageCell` deliberately untouched — it renders file/folder coverage, which has no status. JSON gains `coverage.status`/`lastCommitWithCoverage`/`statusUpdatedAt`/`valueUpdatedAt` on both commands (`valueUpdatedAt` is what tells a consumer the `Waiting` value is stale — the job the glyph does in the table); `pickDeep` drops undefined, so a `None` repo emits `{"status":"None"}` and a status-less one gains no keys (39 new tests, 664 total) |
98
97
| 2026-09-21 | (OD-710) New `images` (`imgs`) and `image` (`img`) commands — container images with SBOMs uploaded to an organization, and the tags under them. Built entirely on operations already present in the generated client (`SbomService`): **no `npm run update-api`**. `images <provider> <org>` lists images (Image / Latest Tag / Last Upload / Last Generated) in a single request. `image <provider> <org> <image>` lists that image's tags (Tag / Environment / Repository / Generated / Uploaded / Last Analysed), shows one with `-t, --tag <tag>`, and deletes with `-D, --delete`. **`--delete` is the action and `--tag` is the scope**, the same split `issues --ignore` makes with its filters — the flag that narrows what is acted on is the flag that narrows what is shown — so `--delete` alone removes the image and every SBOM under it, `--tag X --delete` removes one tag, and there is no second delete verb needing a mutual-exclusion guard. `--tag` without an action pages the listing and matches exactly (the tags endpoint has no per-tag filter — the same shape `pull-request --issue <id>` uses) and errors naming the tag when absent; with `--delete` it skips the lookup, since the API 404s on a missing tag at lower cost. **No tag count on the `images` table**: it is the number an org at the 1000-tag cap actually wants, but `ImageSummary` doesn't carry one and deriving it costs one request per image, so it is being added server-side instead (pending task above) rather than fanned out from the client. A whole-image `--delete` still reads a `limit: 1` `pagination.total` so its prompt can name how many tags are about to go; a failed lookup drops the count rather than blocking, and `-y` skips it. Both delete scopes go through the shared `confirmAction` (non-TTY without `-y` aborts, as in `issues --ignore`) and print a yellow notice first: a single SBOM delete currently zero-fills Container Scanning metrics for the **whole organization** until the next nightly scan. That defect is also why this is the first of three stacked PRs — bulk cleanup (`--keep-latest`) would fire it once per tag and waits on the fix; `--upload` is PR 2. Both commands are **account-token only** — no image operation is on the 14-operation repository-token whitelist — and are covered by the cross-cutting `repository-token-refusals.test.ts` rather than their own token tests. Image/tag/environment/repository names arrive with the SBOM upload, so all four go through `sanitizeText()` (26 new tests, 699 total) |
99
98
| 2026-09-21 | (OD-710, PR 2) `image --upload <file>`: push an SBOM (SPDX or CycloneDX) for one image tag, via the already-generated `uploadImageSbom`. Fits the command's existing action/scope split — `--upload` is a verb, `--tag` scopes it — and **requires `--tag`**, since the API keys the upload on image *and* tag with no untagged fallback; that is refused by name before the file is read. The file is validated locally first (unreadable path, empty file), so the common mistakes fail with something actionable instead of a 400 from the other side of the network. Sent as a `File` rather than a bare `Blob` so the multipart part carries the real filename — a `Blob` goes out as `filename="blob"` — with the media type inferred from the extension (`.json`/`.xml`, else `application/octet-stream`, letting the API decide rather than guessing wrong in the request); the generated `isBlob` accepts both shapes. Optional `-e, --environment` and `-r, --repository` map to the API's `environment`/`repositoryName` and are omitted from the form rather than sent as undefined. `--upload` and `--delete` are refused together: unlike `--delete`'s two scopes these are two different verbs, and asking for both says nothing coherent about what should happen to the SBOM. Still account-token only, which is the awkward part — uploading from a pipeline is exactly where a project token would be natural, so the gap is logged in `SPECS/missing-endpoints.md` (9 new tests, 708 total) |
100
-
| 2026-09-21 | (OD-710, PR 3) `image --delete --keep-latest <n>` + `--dry-run`: the release-pipeline cleanup step, keeping the n most recently uploaded tags of an image and deleting every older one. **Shape is a deliberate departure from the design proposal.** `container-tagging-guidance/AGENTS.md` §2 (CLI-1) proposed `image delete-tags <image> --keep-latest N` and marked it "Builder's call"; `--delete` is already the verb here and `--tag` already the scope, so `--keep-latest` is simply one more scope ("all but the newest n") rather than a second command doing almost the same thing, and this CLI avoids its first nested subcommand. The proposal's goal ("verb first, names what it deletes") is met, and the Figma snippet needed changing regardless — `research/setup-copy-and-snippet.md` §7 lists its verbless `codacy image ${IMAGE_NAME} --keep-latest 19` as an outstanding defect. **Cleanup runs before the upload** (forced by `upsertImageTag` raising in-transaction at the cap, so upload-then-delete strands an org there), which sets three behaviours: `n` is **literal** — it counts what exists when it runs, not after the upload, so cleanup-first with 10 leaves 11 and making n secretly mean n-1 would print a number the user didn't type; "nothing to delete" **exits 0**, since an org under n is the common case on every release; and a failed delete **does not stop the loop** — giving up at tag 3 of 80 leaves the org no better off — though the exit code is still 1, because a partial cleanup is a real failure for the step behind it. Ordered by `uploadedAt`, not `generatedAt`: "latest" means when Codacy received the SBOM, which is what accumulates against the cap. Deletes are **sequential**, since each one currently zero-fills org-wide metrics and firing dozens at once is the worst shape for that. **Org-budget warning**: the cap is org-wide and counts image × tag rows while this flag is per image, so `--keep-latest 10` puts a 212-image org at 2,120 against a 1,000 cap while appearing to follow instructions (`research/per-image-tag-budget.md`). That file's rule — never quote a constant n without the image count beside it — is honoured by reading the image count (one request, `limit: 1`) and warning when `n × images` exceeds the cap, naming the count and what the cap allows per image; it **warns rather than refuses** and deliberately does not pick between that file's four unowned options. `DEFAULT_ORG_TAG_CAP` is 1,000 but the cap is configuration (`sbom.image.max-image-tags-per-org`) that no endpoint exposes, so the copy says "default" and admits the CLI cannot read it, with exact figures rather than `formatCount`'s "1k"/"2.1k". `--dry-run` is **long-only**, a documented exception to the short-flag rule: every free letter sits one shift-key from `-D, --delete` and that typo is the destructive one. **Not merged**: blocked on confirming the metrics-wipe fix (pending table above) (13 new tests, 721 total) |
99
+
| 2026-09-21 | (OD-710, PR 3) `image --delete --keep-latest <n>` + `--dry-run`: the release-pipeline cleanup step, keeping the n most recently uploaded tags of an image and deleting every older one. **Shape is a deliberate departure from the design proposal.** `container-tagging-guidance/AGENTS.md` §2 (CLI-1) proposed `image delete-tags <image> --keep-latest N` and marked it "Builder's call"; `--delete` is already the verb here and `--tag` already the scope, so `--keep-latest` is simply one more scope ("all but the newest n") rather than a second command doing almost the same thing, and this CLI avoids its first nested subcommand. The proposal's goal ("verb first, names what it deletes") is met, and the Figma snippet needed changing regardless — `research/setup-copy-and-snippet.md` §7 lists its verbless `codacy image ${IMAGE_NAME} --keep-latest 19` as an outstanding defect. **Cleanup runs before the upload** (forced by `upsertImageTag` raising in-transaction at the cap, so upload-then-delete strands an org there), which sets three behaviours: `n` is **literal** — it counts what exists when it runs, not after the upload, so cleanup-first with 10 leaves 11 and making n secretly mean n-1 would print a number the user didn't type; "nothing to delete" **exits 0**, since an org under n is the common case on every release; and a failed delete **does not stop the loop** — giving up at tag 3 of 80 leaves the org no better off — though the exit code is still 1, because a partial cleanup is a real failure for the step behind it. Ordered by `uploadedAt`, not `generatedAt`: "latest" means when Codacy received the SBOM, which is what accumulates against the cap. Deletes are **sequential** — one request at a time is what makes "deleted 77 of 80, here are the 3 that failed" reportable; the org-wide metrics wipe that originally forced it is fixed. **Org-budget warning**: the cap is org-wide and counts image × tag rows while this flag is per image, so `--keep-latest 10` puts a 212-image org at 2,120 against a 1,000 cap while appearing to follow instructions (`research/per-image-tag-budget.md`). That file's rule — never quote a constant n without the image count beside it — is honoured by reading the image count (one request, `limit: 1`) and warning when `n × images` exceeds the cap, naming the count and what the cap allows per image; it **warns rather than refuses** and deliberately does not pick between that file's four unowned options. `DEFAULT_ORG_TAG_CAP` is 1,000 but the cap is configuration (`sbom.image.max-image-tags-per-org`) that no endpoint exposes, so the copy says "default" and admits the CLI cannot read it, with exact figures rather than `formatCount`'s "1k"/"2.1k". `--dry-run` is **long-only**, a documented exception to the short-flag rule: every free letter sits one shift-key from `-D, --delete` and that typo is the destructive one. Shipped once [Fix org-wide metrics wipe on image tag deletion](https://linear.app/codacy/project/fix-org-wide-metrics-wipe-on-image-tag-deletion-197476700869/overview) landed, which is what had held a delete *loop* back while PR 1's single-tag and whole-image deletes went ahead; `METRICS_WIPE_NOTICE` was removed with it. Verified end to end against `gh/claudiacodacy` with five Trivy-generated CycloneDX SBOMs: upload x5, list, dry-run, apply, idempotent re-run, single-tag delete, whole-image delete (13 new tests, 721 total) |
Copy file name to clipboardExpand all lines: SPECS/commands/images.md
+15-18Lines changed: 15 additions & 18 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,21 +18,16 @@ Split into stacked PRs, because one piece is blocked on a backend fix:
18
18
|---|---|---|
19
19
| 1 |`images` (list) and `image` (list tags, show a tag, `--delete` scoped by `--tag`) | done |
20
20
| 2 |`--upload` (`uploadImageSbom`) | done |
21
-
| 3 | bulk cleanup — `--delete --keep-latest <n>`| this one — **see the blocker below**|
22
-
23
-
**The bulk-cleanup blocker, unresolved as of 2026-09-21.** Every single tag
24
-
delete currently zero-fills Container Scanning metrics for the *whole
25
-
organization*, across every repository, healing only on the next nightly scan.
26
-
`--keep-latest` deletes in a loop, so it fires that once per tag — 80 times for
27
-
the org this exists for. OD-710 records the fix (Container Scanning Findings
28
-
Integrity, milestone "Fix org-wide metrics wipe on image tag deletion") as
29
-
"mostly landed"; nobody has confirmed it shipped. **PR 3 must not merge until
30
-
someone does.** Single-tag and whole-image delete were safe to ship ahead of it
31
-
and did, in PR 1.
32
-
33
-
Every delete path prints a yellow notice above the confirmation saying the
34
-
metrics will be zeroed and restored by the next nightly scan. Remove
35
-
`METRICS_WIPE_NOTICE` and this section together when the fix is confirmed.
21
+
| 3 | bulk cleanup — `--delete --keep-latest <n>`| this one |
22
+
23
+
**The bulk-cleanup blocker is resolved (2026-09-21).** Every tag delete used to
24
+
zero-fill Container Scanning metrics for the *whole organization* until the next
25
+
nightly scan, which is why `--keep-latest` — a delete loop — was held back while
26
+
single-tag and whole-image delete shipped in PR 1. The backend fix
27
+
([Fix org-wide metrics wipe on image tag deletion](https://linear.app/codacy/project/fix-org-wide-metrics-wipe-on-image-tag-deletion-197476700869/overview))
28
+
is done, so the loop is safe and the warning notice that used to sit above every
29
+
delete confirmation is gone. Deletes are still sequential, for the reasons in
30
+
`deleteTagsInSequence`, but no longer because of this.
36
31
37
32
## API
38
33
@@ -113,7 +108,7 @@ errors naming the tag when there is no match. JSON emits one object, not an
113
108
array. `--tag --delete` skips the lookup entirely and deletes straight away; the
114
109
API 404s on a tag that isn't there, which is the same answer at a lower cost.
115
110
116
-
**Confirmation.**Both delete scopes prompt via the shared `confirmAction`
111
+
**Confirmation.**Every delete scope prompts via the shared `confirmAction`
117
112
(`utils/prompt.ts`) and proceed only on an explicit `y`; `-y` bypasses it for CI.
118
113
`confirmAction` returns `false` on a non-TTY, so a non-interactive run without
119
114
`-y` aborts rather than deleting by accident — same rule as `issues --ignore`.
@@ -191,8 +186,10 @@ upload-then-delete *fails at the cap* and can strand an org there
191
186
uploads per release means when Codacy received the SBOM; `generatedAt` is when
192
187
it was built, which can differ and is not what accumulates against the cap.
193
188
194
-
**Deletes run sequentially.** Each one currently zero-fills organization-wide
195
-
metrics, so firing dozens in parallel is the worst possible shape for it.
189
+
**Deletes run sequentially.** Not for latency — a cleanup run happens before the
190
+
upload, not in front of a waiting user — but because one request at a time is
191
+
what makes "deleted 77 of 80, here are the 3 that failed" straightforward to
192
+
report. (The original reason, the org-wide metrics wipe, is fixed.)
196
193
197
194
**`--dry-run` is long-only** — a deliberate exception to the "every option gets
198
195
a short flag" rule. Every free letter sits one shift-key from `-D, --delete`,
0 commit comments