diff --git a/CHANGELOG.md b/CHANGELOG.md index 9703455ca..5c4a4f6c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Demo site favicon now matches the refreshed branding.** The v1.5.1 brand refresh (#439) moved the website to the cropped whale "headshot" icon and the app UI followed, but demo.getdrydock.com kept showing the old full-body whale: its stale `favicon.svg` — which modern browsers preferred over the PNGs — was never replaced. The demo now ships the same headshot icon set as the website and app UI, the `favicon.svg` is removed, and the icon links carry a `?v=2` cache-buster so browsers re-fetch instead of serving the aggressively cached old icon. (#689) +## [1.6.0] — 2026-08-11 + +Consolidates the `1.6.0-rc.1` … `1.6.0-rc.13` prereleases. Users upgrading from +`1.5.2` get everything below; users already on `1.6.0-rc.13` receive no additional +runtime changes. + +### Added + +- **Portwing edge and agent integration matures into a first-class transport.** Portwing 0.9.0+ agents work over either inbound Standard HTTP or dial-out Edge WebSocket transport, and drydock 1.6.0-rc.11+ recognizes Portwing's own Docker watcher marker to run native registry checks plus single/batch Docker updates controller-side ([#632](https://github.com/CodesWhat/drydock/issues/632), [#637](https://github.com/CodesWhat/drydock/issues/637), [Portwing #76](https://github.com/CodesWhat/portwing/issues/76)). Standard-mode agents can sign every request with Ed25519 (`DD_AGENT_{name}_AUTHMODE=ed25519`, upgraded mid-series to signature version 2) instead of sending a shared secret. Edge agents stream live container logs and process deletes over the existing WS tunnel with correlated per-request IDs, pick their own display name (bound to their signing key so it can't be squatted), and are proactively disconnected when they stop answering pings. The `portwing/1.0` edge endpoint is enabled by default (`DD_EXPERIMENTAL_PORTWING=false` remains an emergency disable), backed by a real fleet-soak workflow that runs signed Portwing processes against production infrastructure on every PR and on a weekly schedule. +- **Declarative, three-tier update policy with a maturity stabilization gate** ([Discussion #307](https://github.com/CodesWhat/drydock/discussions/307), [Discussion #406](https://github.com/CodesWhat/drydock/discussions/406), [#320](https://github.com/CodesWhat/drydock/issues/320)). Containers can declare `dd.updatePolicy.*` labels (maturity mode/min-age, skip tags, skip digests) with watcher-level defaults and UI/API overrides, resolving through label → watcher default → persistent override precedence and surviving agent refresh, container recreation, and error-state rebuilds. A candidate held back by `maturityMode: mature` is visible immediately with a live minute-by-minute countdown to its unlock time, and a new `maturity-cleared` notification (backed by a 5-minute background sweep) announces the moment it clears instead of waiting for the next scan. **Update Now** remains available as an explicit soft-policy override. +- **Per-rule notification templates, bell preferences, and a new health-status event** ([Discussion #205](https://github.com/CodesWhat/drydock/discussions/205), [Discussion #198](https://github.com/CodesWhat/drydock/discussions/198), [Discussion #210](https://github.com/CodesWhat/drydock/discussions/210)). The Notifications view can override title/body/batch-title text per rule and provider with a live preview, and independently control which event categories reach the in-app bell — including a new `container-unhealthy` rule (disabled by default) that fires off Docker health-check transitions. Bidirectional MQTT lets Home Assistant's Install button trigger a real drydock update through the normal eligibility/dispatch path, rate-limited per container and audited. `dd.action.*`/`dd.notification.*` labels are now strictly scoped to their own trigger category instead of one silently gating the other ([#494](https://github.com/CodesWhat/drydock/issues/494)). +- **Global update mode and an actionable Update Status panel** ([Discussion #325](https://github.com/CodesWhat/drydock/discussions/325)). **Settings → General** now selects one server-wide mode — `notify`, `manual`, or `auto` — and the container side panel and detail view replace the old eligibility-badge stack with plain-language status plus a structured, deep-linking condition list covering all 16 eligibility reasons. Existing installs migrate to `auto` on upgrade so configured automatic updates keep working; fresh installs default to `manual` (see Upgrade Notes). +- **Cross-device preference sync and a zero-dependency dashboard grid** ([Discussion #220](https://github.com/CodesWhat/drydock/discussions/220), [#281](https://github.com/CodesWhat/drydock/issues/281)). An opt-in **Sync across devices** toggle stores the full UI preference set server-side per user and propagates changes to a user's other signed-in sessions in real time over SSE. The dashboard grid drops its `grid-layout-plus` dependency for a deterministic CSS Grid implementation with edit-mode drag/reorder, bounded resizing, touch support, and per-breakpoint layout persistence. Audit, Security, Servers, and Watchers gain the same per-view column show/hide picker Containers and Agents already had. +- **More signal, less noise in update visibility.** A pinned tag now surfaces the best newer same-family tag as a purely informational badge without making it an actionable update candidate ([#498](https://github.com/CodesWhat/drydock/issues/498)); a new **Version Update** filter isolates real semver bumps from digest-only churn on fleets that rebuild images daily ([#538](https://github.com/CodesWhat/drydock/issues/538)); and a startup warning flags minute-precise maintenance-window crons that only open their window for one minute per matching hour instead of the intended full range ([Discussion #639](https://github.com/CodesWhat/drydock/discussions/639)). +- **Opt-in wud-card/Homepage compatibility endpoints.** With the unversioned `/api/*` alias removed (see Removed, below), `DD_COMPAT_WUDCARD=true` (default `false`) mounts a narrow compatibility layer covering exactly the four endpoints the Home Assistant [wud-card](https://github.com/angryvoegi/wud-card) integration and Homepage's `whatsupdocker` widget call, reshaped into the bare-array response those integrations expect. ([Discussion #469](https://github.com/CodesWhat/drydock/discussions/469)) + +### Changed + +- **Every major list view is responsive and consistent.** Containers, Agents, Notifications, Security, Triggers, Watchers, Servers, Registries, Audit, and Auth now share one `DataTable` with a persisted per-view table⇄card toggle that auto-reflows to cards below ~640px; card views gained a sort control; and Source/Release-notes/Registry resource shortcuts render through one consistent 44px toolbar everywhere they appear ([#498](https://github.com/CodesWhat/drydock/issues/498), [Discussion #295](https://github.com/CodesWhat/drydock/discussions/295)). The Dashboard and Containers views no longer overflow horizontally on narrow phone screens. Update-status vocabulary was overhauled ("Digest update", "Security hold", neutral Major/Minor/Patch badges for informational-only updates), pinned reverted to being a tag property rather than an update status, and the maturity panel now shows exactly one countdown clock instead of two that could disagree with the gate ([#556](https://github.com/CodesWhat/drydock/issues/556)). Table polish across the series fixed pinned-column overlay clipping, a registry error overwriting the visible tag, sticky-column/auto-hide width miscalculation, and a clipped icon column. +- **Trigger taxonomy migration reaches its final warning stage.** Every `DD_TRIGGER_*` variable and the deprecated `dd.trigger.include`/`dd.trigger.exclude` labels still work in v1.6 but now log at `error` level ahead of removal in v1.7; use `DD_ACTION_*`/`DD_NOTIFICATION_*` and category-scoped labels, or run `config migrate --source trigger`. +- **Less duplicate work on the hot paths.** Registry tag-list requests are shared across containers within a poll instead of being repeated per container; scheduled security scans and the shared log viewer avoid redundant store writes and re-renders; and transient registry network errors (timeouts, connection resets, DNS blips) now retry with backoff before a watch error is recorded. +- **Base image bumped from Alpine 3.21 to 3.24**, later rebased onto `node:24-alpine` at Node 24.19.0 with a matching Trivy build-stage bump to 0.73.0, closing several HIGH/MEDIUM CVEs in the underlying image ([#682](https://github.com/CodesWhat/drydock/pull/682)). + +### Deprecated + +- **`GET /api/auth/methods`** and the legacy `GET /auth/strategies` response shape are deprecated in v1.6.0 (removal in v1.7.0 and v1.8.0 respectively). Both now log on every request and return `Deprecation`/`Sunset` headers pointing callers at canonical `GET /api/v1/auth/status`. See [DEPRECATIONS.md](./DEPRECATIONS.md). + +### Removed + +- **Unversioned `/api/*` and `WS /api/log/stream` aliases are gone**, returning `410 Gone` (or a rejected upgrade) instead of serving the request. This is a breaking change for integrations that hardcode the unversioned base path, notably the Home Assistant [wud-card](https://github.com/angryvoegi/wud-card) integration and Homepage's `whatsupdocker` widget — use the `DD_COMPAT_WUDCARD` shim above, or migrate to `/api/v1/*` directly. +- **Legacy v1.4-era authentication compatibility.** Basic auth now accepts only argon2id hashes (`{SHA}`, APR1/MD5, crypt, and plain-text hashes fail validation), and OIDC discovery now requires `https://` with no insecure HTTP workaround. +- **Legacy WUD configuration aliases, obsolete watcher switches, and legacy trigger-template variables.** `WUD_*` environment variables and `wud.*` labels are ignored (the migration CLI still recognizes them so existing files can be rewritten); `DD_WATCHER__WATCHDIGEST`/`WATCHATSTART` are no longer configuration keys (use `dd.watch.digest=true`; startup watches always run); and `$id`/`$name`/`$watcher`/`$kind`/`$semver`/`$local`/`$remote`/`$link`/`$count` no longer populate in trigger templates. +- **Kafka `clientId` and token-only public-registry compatibility handling.** Kafka validation accepts only lowercase `clientid`, and malformed public-registry configurations (a bare `PUBLIC_TOKEN` with no `PUBLIC_LOGIN`) now fail closed instead of silently falling back to anonymous pulls. + +### Fixed + +- **The maturity soak clock, update-policy overrides, and digest identity now survive the paths that used to reset them.** Container recreation, agent report ingestion, watch errors, concurrent security scans, and manual rechecks each previously had at least one path that could silently restart a maturity countdown, drop a stored policy override, or roll back freshly detected update state — every one of those is closed, including the store's digest anchor now self-healing from a stale, multi-entry `RepoDigests` ordering instead of trusting whichever entry happened to land first ([#669](https://github.com/CodesWhat/drydock/issues/669)). Notification dedup no longer fires a duplicate one-shot notification when a manual recheck returns drifted metadata for the same candidate, and containers with no available update are no longer ranked or filtered as though they had one. +- **Agent- and edge-specific reliability fixes.** The container list no longer flashes a false "Agent Mismatch" during an agent's brief re-registration window; start/stop/restart/rollback return an explicit `501` instead of an ambiguous `404` when an agent lacks lifecycle transport ([#637](https://github.com/CodesWhat/drydock/issues/637)); WebSocket log streams accept anonymous-auth sessions; and edge `memoryGb` now reports binary GiB instead of decimal GB. +- **Operational fixes.** The Star History chart is now self-hosted after the third-party chart services' outage ([#671](https://github.com/CodesWhat/drydock/issues/671)); the icon bundle no longer silently drops icons that became iconify aliases mid-series; `GET /api/v1/containers/backups` is reachable again (it was shadowed by the container router's own root handler); infrastructure-mode self-updates no longer fail to spawn their helper container on Docker Hub installs ([#644](https://github.com/CodesWhat/drydock/issues/644)); unchanged pending updates stop bloating the audit log on a timer; and the app and demo favicons match the v1.5.1 brand refresh (#439). + +### Security + +- **Anonymous access fails closed on upgrade, not just on fresh installs.** An unconfigured or unconfirmed-anonymous instance now rejects protected API requests with `401` instead of showing an open dashboard; set `DD_ANONYMOUS_AUTH_CONFIRM=true` to keep an intentionally open instance working. +- **Transport and session hardening.** HTTP notification triggers are hardened against SSRF (blocked cloud-metadata/link-local targets, contained redirects); WebSocket upgrades validate the complete origin instead of a host substring; the session cookie is renamed to `drydock.sid`, signing every existing user out once; `/store` and its contents are created owner-only (`0700`/`0600`); and icon CDN sources are pinned to exact upstream revisions instead of floating tags. +- **Ongoing dependency and base-image CVE sweeps ran throughout the series** — notably Next.js 16.2.11 and React 19.2.8 on the documentation site, `fast-uri`/`ip-address`/`brace-expansion` override advances, and the Node 24.19.0 + Trivy 0.73.0 bump in the final release candidate. See the individual `rc.*` entries below for full CVE detail. + +### Upgrade Notes + +- **Existing installs keep automatic updates; fresh installs start in manual mode.** The new global `updateMode` setting defaults to `manual` only when drydock creates a settings record for the first time; an existing record with no `updateMode` migrates to `auto`, preserving pre-v1.6 behavior. Review **Settings → General → Update mode** after upgrading if you prefer notifier-only or manual-only operation. +- **A lone `dd.action.include`/`dd.action.exclude` (or `dd.notification.include`/`dd.notification.exclude`) label stops filtering the other trigger category.** If you relied on the pre-v1.6 cross-category leak (#494), previously-suppressed notification (or action) triggers may fire once on the first scan after upgrading. drydock logs a one-time warning per affected container naming the missing label; set the matching label to the same value to restore the previous filtering. + +### Documentation + +- **Podman/Docker socket security docs refreshed**, and a full configuration/env-var and API/OpenAPI parity audit brought the docs and generated spec in line with the shipped `/api/v1` surface, including the authentication component, registry webhook signature auth, notification outbox actions, bulk container updates, backup listing, and container summary/update-response details. + ## [1.6.0-rc.13] — 2026-08-08 ### Security @@ -2345,7 +2400,8 @@ Remaining upstream-only changes (not ported — not applicable to drydock): | Fix codeberg tests | Covered by drydock's own tests | | Update changelog | Upstream-specific | -[Unreleased]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.13...HEAD +[Unreleased]: https://github.com/CodesWhat/drydock/compare/v1.6.0...HEAD +[1.6.0]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.13...v1.6.0 [1.6.0-rc.13]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.12...v1.6.0-rc.13 [1.6.0-rc.12]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.11...v1.6.0-rc.12 [1.6.0-rc.11]: https://github.com/CodesWhat/drydock/compare/v1.6.0-rc.10...v1.6.0-rc.11 diff --git a/README.md b/README.md index 6f22080bb..31dfb5031 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@

- Version + Version Multi-arch License AGPL-3.0
@@ -178,6 +178,20 @@ See the [Quick Start guide](https://getdrydock.com/docs/quickstart) for Docker C

🆕 Recent Updates

+v1.6.0 highlights + +- **Portwing edge/agent transport matures** — controller-owned native Docker checks/updates for Portwing 0.9.0+, continuous edge log streaming, Ed25519 request signing (v2), and agent-owned display names bound to their signing key. ([#632](https://github.com/CodesWhat/drydock/issues/632), [#637](https://github.com/CodesWhat/drydock/issues/637)) +- **Declarative update policy with a maturity stabilization gate** — three-tier `dd.updatePolicy.*` precedence, a live countdown to a held-back candidate's unlock time, and a dedicated `maturity-cleared` notification. ([Discussion #307](https://github.com/CodesWhat/drydock/discussions/307), [Discussion #406](https://github.com/CodesWhat/drydock/discussions/406)) +- **Per-rule notification templates, bell preferences, and a new `container-unhealthy` event**, plus bidirectional Home Assistant MQTT (Install button triggers a real update). ([Discussion #205](https://github.com/CodesWhat/drydock/discussions/205), [Discussion #198](https://github.com/CodesWhat/drydock/discussions/198)) +- **Every major list view is responsive** — one shared `DataTable` with a persisted table⇄card toggle across all ten list views, reflowing to cards below ~640px. ([#498](https://github.com/CodesWhat/drydock/issues/498)) +- **`/api/v1` parity completes** — the unversioned `/api/*` alias and `WS /api/log/stream` are removed (`410 Gone`); an opt-in `DD_COMPAT_WUDCARD` shim covers wud-card/Homepage. ([Discussion #469](https://github.com/CodesWhat/drydock/discussions/469)) +- **Security hardening** — anonymous access fails closed on upgrade (not just fresh installs), HTTP triggers are SSRF-hardened, WebSocket origin checks are full-origin, and the session cookie is renamed to `drydock.sid`. + +Full release notes in [CHANGELOG.md](./CHANGELOG.md#160--2026-08-11). + +
+ +
v1.6.0-rc.13 highlights - **Digest comparison anchors on repo-matched candidates** — `getOrderedRepoDigests` filters a container's `RepoDigests` to entries whose repo component matches its own image reference before comparing, instead of trusting an arbitrary index-0 entry; a store already poisoned with a stale anchor self-heals. ([#670](https://github.com/CodesWhat/drydock/pull/670)) diff --git a/apps/demo/src/mocks/data/agents.ts b/apps/demo/src/mocks/data/agents.ts index a9c3d39b4..fef70fa92 100644 --- a/apps/demo/src/mocks/data/agents.ts +++ b/apps/demo/src/mocks/data/agents.ts @@ -4,7 +4,7 @@ export const agents = [ host: '192.168.1.50', port: 3001, connected: true, - version: '1.6.0-rc.13', + version: '1.6.0', os: 'linux', arch: 'amd64', cpus: 4, diff --git a/apps/demo/src/mocks/data/audit.ts b/apps/demo/src/mocks/data/audit.ts index 52c2e33cd..e698bf4c5 100644 --- a/apps/demo/src/mocks/data/audit.ts +++ b/apps/demo/src/mocks/data/audit.ts @@ -3,7 +3,7 @@ export const auditEntries = [ id: 'aud-001', timestamp: '2026-03-10T08:00:00.000Z', action: 'system:start', - details: 'Drydock v1.6.0-rc.13 started', + details: 'Drydock v1.6.0 started', }, { id: 'aud-002', @@ -207,6 +207,6 @@ export const auditEntries = [ timestamp: '2026-03-03T18:00:00.000Z', action: 'container:watch', container: 'drydock', - details: 'Started watching ghcr.io/codeswhat/drydock:1.6.0-rc.13', + details: 'Started watching ghcr.io/codeswhat/drydock:1.6.0', }, ]; diff --git a/apps/demo/src/mocks/data/containers.ts b/apps/demo/src/mocks/data/containers.ts index 4aca6a2e3..3271d9209 100644 --- a/apps/demo/src/mocks/data/containers.ts +++ b/apps/demo/src/mocks/data/containers.ts @@ -330,7 +330,7 @@ export const containers = [ displayName: 'Drydock', displayIcon: 'sh-drydock', image: 'codeswhat/drydock', - tag: '1.6.0-rc.13', + tag: '1.6.0', registryType: 'ghcr', registryUrl: 'https://ghcr.io', scanStatus: 'scanned', diff --git a/apps/demo/src/mocks/data/server.ts b/apps/demo/src/mocks/data/server.ts index 7cf9d4292..5d33ebb9c 100644 --- a/apps/demo/src/mocks/data/server.ts +++ b/apps/demo/src/mocks/data/server.ts @@ -1,5 +1,5 @@ export const serverInfo = { - version: '1.6.0-rc.13', + version: '1.6.0', uptime: 864000, hostname: 'drydock-demo', platform: 'linux', diff --git a/apps/demo/src/mocks/handlers/app.ts b/apps/demo/src/mocks/handlers/app.ts index 474760271..b5c65e547 100644 --- a/apps/demo/src/mocks/handlers/app.ts +++ b/apps/demo/src/mocks/handlers/app.ts @@ -4,7 +4,7 @@ export const appHandlers = [ http.get('/api/v1/app', () => HttpResponse.json({ name: 'Drydock', - version: '1.6.0-rc.13', + version: '1.6.0', description: 'Docker container update manager', repository: 'https://github.com/CodesWhat/drydock', documentation: 'https://getdrydock.com/docs', @@ -16,7 +16,7 @@ export const appHandlers = [ return HttpResponse.json( { generatedAt: new Date().toISOString(), - server: { version: '1.6.0-rc.13', mode: 'demo' }, + server: { version: '1.6.0', mode: 'demo' }, summary: { containers: 25, watchers: 2, diff --git a/apps/web/src/lib/site-config.ts b/apps/web/src/lib/site-config.ts index f10c8c58d..06a3fd85a 100644 --- a/apps/web/src/lib/site-config.ts +++ b/apps/web/src/lib/site-config.ts @@ -15,7 +15,7 @@ export const SITE_CONFIG = { /** Brand name shown in the header, footer, and metadata. */ name: "Drydock", /** Current release version shown in the hero badge. */ - version: "1.6.0-rc.13", + version: "1.6.0", /** Short product tagline used in page titles and OG metadata. */ tagline: "Container Update Monitoring", /** Default meta / OpenGraph / Twitter description. */ diff --git a/apps/web/src/lib/site-content.ts b/apps/web/src/lib/site-content.ts index 90731abc8..0cba76bb4 100644 --- a/apps/web/src/lib/site-content.ts +++ b/apps/web/src/lib/site-content.ts @@ -281,7 +281,7 @@ export const roadmap: Milestone[] = [ ], }, { - version: "v1.6.0-rc.13", + version: "v1.6.0", title: "Notifications, Policy & Release Intel", emoji: "\u{1F4E8}", status: "next", diff --git a/content/docs/current/api/agent.mdx b/content/docs/current/api/agent.mdx index 6bcc65f74..8f84f7a53 100644 --- a/content/docs/current/api/agent.mdx +++ b/content/docs/current/api/agent.mdx @@ -23,7 +23,7 @@ curl http://drydock:3000/api/v1/agents "host": "192.168.1.50", "port": 3000, "connected": true, - "version": "1.6.0-rc.13", + "version": "1.6.0", "os": "linux", "arch": "amd64", "cpus": 4, @@ -153,7 +153,7 @@ Sent immediately upon connection to confirm the handshake. { "type": "dd:ack", "data": { - "version": "1.6.0-rc.13", + "version": "1.6.0", "os": "linux", "arch": "amd64", "cpus": 4, diff --git a/content/docs/current/api/app.mdx b/content/docs/current/api/app.mdx index 629a6a2b7..6d4f55cbb 100644 --- a/content/docs/current/api/app.mdx +++ b/content/docs/current/api/app.mdx @@ -12,7 +12,7 @@ curl http://drydock:3000/api/v1/app { "name":"drydock", - "version":"1.6.0-rc.13" + "version":"1.6.0" } ``` diff --git a/content/docs/current/api/portwing.mdx b/content/docs/current/api/portwing.mdx index 978642099..da94b191f 100644 --- a/content/docs/current/api/portwing.mdx +++ b/content/docs/current/api/portwing.mdx @@ -197,7 +197,7 @@ A versioned alias `/api/v1/portwing/ws` is also accepted and is signature-equiva "agentId": "edge-host-01", "agentName": "edge-host-01", "protocol": "portwing/1.0", - "version": "1.6.0-rc.13", + "version": "1.6.0", "pubKeyId": "3f8a1c2e9b047d56", "timestamp": 1780329600, "nonce": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", @@ -220,7 +220,7 @@ A name collision with an already-connected agent under the same key (or the in-f "data": { "pollInterval": 300, "config": { - "drydockVersion": "1.6.0-rc.13", + "drydockVersion": "1.6.0", "supportedProtocols": "portwing/1.0", "serverCompatLevel": "1.4.0" } diff --git a/content/docs/current/quickstart/index.mdx b/content/docs/current/quickstart/index.mdx index 6c98bf775..dd481040c 100644 --- a/content/docs/current/quickstart/index.mdx +++ b/content/docs/current/quickstart/index.mdx @@ -108,7 +108,7 @@ Release tags use the same channel names in every registry: | Tag | Behavior | | --- | --- | -| `1.6.0-rc.13` | Immutable release candidate; best for reproducible testing | +| `1.6.0` | Immutable exact GA release; best for reproducible deployments | | `1.6-rc` | Rolling release-candidate channel; moves to the newest `1.6.0-rc.N` | | `1.6` | Rolling stable minor channel; published only for GA releases | | `1` | Rolling stable major channel; published only for GA releases | diff --git a/content/docs/current/updates/index.mdx b/content/docs/current/updates/index.mdx index 9ee941dd9..9a508c53b 100644 --- a/content/docs/current/updates/index.mdx +++ b/content/docs/current/updates/index.mdx @@ -5,6 +5,17 @@ description: "Release update notes and feature highlights, with direct links to ## Unreleased +## v1.6.0 Highlights — August 11, 2026 + +Consolidates the `1.6.0-rc.1` … `1.6.0-rc.13` prereleases into one GA release. The +biggest themes: Portwing edge/agent transport maturing into a first-class citizen, +a declarative three-tier update policy with a maturity stabilization gate, +per-rule notification templates and a new health-status event, every major list +view going responsive with a consistent table/card toggle, `/api/v1` parity with +the unversioned `/api/*` alias removed, and fail-closed security hardening +throughout. See [CHANGELOG.md](https://github.com/CodesWhat/drydock/blob/main/CHANGELOG.md#160--2026-08-11) +for the full release notes. + ## v1.6.0-rc.13 Highlights — August 8, 2026 - **Digest comparison anchors on repo-matched candidates** — `getOrderedRepoDigests` (`app/watchers/providers/docker/docker-helpers.ts`) filters a container's `RepoDigests` entries to the ones whose repo component matches its own image reference before comparing, instead of trusting an arbitrary `RepoDigests[0]`; a store already poisoned with a stale digest anchor self-heals on the next cycle ([#670](https://github.com/CodesWhat/drydock/pull/670)). diff --git a/scripts/changelog-links.test.mjs b/scripts/changelog-links.test.mjs index 36a63c82f..988453894 100644 --- a/scripts/changelog-links.test.mjs +++ b/scripts/changelog-links.test.mjs @@ -53,10 +53,11 @@ test('every linked changelog heading has exactly one link definition', () => { ); }); -test('v1.6 RC and v1.5.2 GA have a complete chronological comparison-link chain', () => { +test('v1.6.0 GA and v1.5.2 GA have a complete chronological comparison-link chain', () => { const definitions = new Map(getLinkDefinitions(changelog).map(({ label, url }) => [label, url])); const expected = new Map([ - ['Unreleased', `${repositoryUrl}/compare/v1.6.0-rc.13...HEAD`], + ['Unreleased', `${repositoryUrl}/compare/v1.6.0...HEAD`], + ['1.6.0', `${repositoryUrl}/compare/v1.6.0-rc.13...v1.6.0`], ['1.6.0-rc.13', `${repositoryUrl}/compare/v1.6.0-rc.12...v1.6.0-rc.13`], ['1.6.0-rc.12', `${repositoryUrl}/compare/v1.6.0-rc.11...v1.6.0-rc.12`], ['1.6.0-rc.11', `${repositoryUrl}/compare/v1.6.0-rc.10...v1.6.0-rc.11`], @@ -120,3 +121,36 @@ test('real changelog exposes nonempty v1.5.2 GA release notes', () => { 'GA notes must contain every rc.1–rc.5 bullet exactly once', ); }); + +test('real changelog exposes nonempty v1.6.0 GA release notes', () => { + const entry = extractChangelogEntry(changelog, 'v1.6.0'); + + assert.match(entry, /^## \[1\.6\.0\] [–—-] \d{4}-\d{2}-\d{2}$/mu); + assert.match(entry, /Consolidates the `1\.6\.0-rc\.1` … `1\.6\.0-rc\.13` prereleases\./u); + assert.match(entry, /^### Added$/mu); + assert.match(entry, /^### Changed$/mu); + assert.match(entry, /^### Fixed$/mu); + assert.match(entry, /^### Security$/mu); + // Two halves, and the first is nearly free: extractChangelogEntry stops at + // the next top-level heading, so on its own it proves almost nothing. The + // one that carries weight is the second, because the rc sections are kept + // below the GA entry on purpose and a rollup that deleted them would still + // satisfy every other assertion here. + assert.doesNotMatch(entry, /^## \[1\.6\.0-rc\.13\]/mu); + assert.match(changelog, /^## \[1\.6\.0-rc\.13\]/mu); + + // v1.6.0 rolls up thirteen release candidates by theme rather than + // concatenating every rc bullet verbatim (unlike the v1.5.2 GA entry + // above, which consolidated a five-candidate series bullet-for-bullet). + // These markers pin the major themes the GA notes must still cover. + for (const marker of [ + 'Portwing edge and agent integration', + 'maturity stabilization gate', + 'Per-rule notification templates', + 'Every major list view is responsive', + 'Unversioned `/api/*` and `WS /api/log/stream` aliases are gone', + 'Anonymous access fails closed', + ]) { + assert.ok(entry.includes(marker), `v1.6.0 GA notes must include: ${marker}`); + } +}); diff --git a/scripts/release-docs-identity.test.mjs b/scripts/release-docs-identity.test.mjs index af1a8fd50..39e93bbaa 100644 --- a/scripts/release-docs-identity.test.mjs +++ b/scripts/release-docs-identity.test.mjs @@ -2,10 +2,10 @@ import assert from 'node:assert/strict'; import { readdirSync, readFileSync } from 'node:fs'; import test from 'node:test'; -const RC_VERSION = '1.6.0-rc.13'; -const PREV_RC_VERSION = '1.6.0-rc.12'; -const RC_DATE = '2026-08-08'; -const RC_DISPLAY_DATE = 'August 8, 2026'; +const RC_VERSION = '1.6.0'; +const PREV_RC_VERSION = '1.6.0-rc.13'; +const RC_DATE = '2026-08-11'; +const RC_DISPLAY_DATE = 'August 11, 2026'; const DOC_ROOTS = ['content/docs/current', 'content/docs/v1.5']; const BROAD_401_CLAIM = /(?:all|every) API (?:call|request)s?(?: (?:is|are) rejected with| returns?) `401`/iu; diff --git a/scripts/release-identity.test.mjs b/scripts/release-identity.test.mjs index 0a4d69d3a..0c3f4c66e 100644 --- a/scripts/release-identity.test.mjs +++ b/scripts/release-identity.test.mjs @@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs'; import test from 'node:test'; const BASE_VERSION = '1.6.0'; -const RC_VERSION = '1.6.0-rc.13'; +const RC_VERSION = '1.6.0'; const DEMO_RELEASE_FIXTURES = [ { path: 'apps/demo/src/mocks/data/server.ts',