diff --git a/.cursor/rules/git-flow.mdc b/.cursor/rules/git-flow.mdc
index 9160f87..e0a04b7 100644
--- a/.cursor/rules/git-flow.mdc
+++ b/.cursor/rules/git-flow.mdc
@@ -1,17 +1,14 @@
---
-description: Git flow — develop for work, main for production; releases via tags.
+description: Git flow — PRs only; topic → develop → main; tags from main
alwaysApply: true
---
-# Git flow (GitHub): develop → main and tagged releases
+# Git flow (GitHub): PRs into develop, then develop → main, then tags
-- **Branches**: Day-to-day work happens on `develop`. The `main` branch is production and must only be updated from `develop` (merge or squash) when the code is ready for release.
-- **Releases**: Before publishing to GitHub or releasing a version:
- 1. Ensure everything is committed on `develop` and **all tests pass** (`make test` or `go test ./...`).
- 2. Merge `develop` into `main` (or open a Pull Request develop → main).
- 3. On `main`, create an annotated tag with the version number (e.g. `v1.0.0`):
- `git tag -a v1.0.0 -m "Release 1.0.0"`
- and push the tag:
- `git push origin v1.0.0`
-- **Versions**: Use semantic versioning (MAJOR.MINOR.PATCH) for tags, e.g. `v1.2.3`.
-- **Never** commit directly to `main` for features or fixes; everything must go through `develop`, then merge into `main` at release time.
+- **No direct pushes** to `develop` or `main`. Do not `git push origin develop` or `git push origin main`. Do not commit on those branches.
+- **Day-to-day:** From an **up-to-date `develop`**, create a topic branch (`feature/…`, `fix/…`, `chore/…`, `security/…`, `docs/…`). Commit there, push **that** branch, open a **PR into `develop`**. Wait for **green CI**, merge, **delete** the topic branch. Pull `develop`. Never PR a topic branch into `main`.
+- **Ship / production:** Only when releasing: open a **PR `develop` → `main`**, wait for **green CI**, merge. Pull `main`.
+- **Tags:** After `main` has the merge, create an **annotated** semver tag on `main` (e.g. `v1.2.3`) and push the tag (`git push origin v1.2.3`). Tagging `develop` or a topic branch is wrong. A merge to `main` without a tag does not publish GHCR/GoReleaser.
+- **Release checks:** Before the develop → main PR / tag, **`make release-check`** must pass (see `release-tests.mdc`). Do not push a release tag if CI or Security is red.
+- **Afterward:** checkout `develop` and pull. If `main` diverged (e.g. squash merge), sync with a **PR `main` → `develop`**, not a direct push.
+- **Versions:** Semantic versioning for tags (`vMAJOR.MINOR.PATCH`). The version cut is a **dedicated PR** (`chore/release-X.Y.Z` → `develop`) — **do not mix** with features, fixes, or dependency bumps. Never bump version on `main`. That PR must include: `VERSION`, CHANGELOG `[Unreleased]` → `[X.Y.Z]`, README badge (and install URLs that pin the tag), **VHS** `docs/demo.gif` (`make install && bash -c "vhs docs/demo.tape"`), man **`.TH`** (`contrib/man/man1/pgwd.1`), **`make port-freebsd-sync`** and **`make port-openbsd-sync`**. Official ports-tree filings (Bugzilla, ports@) are **out of this flow** — personal follow-up, not a release step.
diff --git a/.cursor/rules/release-tests.mdc b/.cursor/rules/release-tests.mdc
index 542ea06..75dd488 100644
--- a/.cursor/rules/release-tests.mdc
+++ b/.cursor/rules/release-tests.mdc
@@ -16,7 +16,7 @@ Before merging to `main` or creating a release (tag and `make release`), run **`
**These are NOT optional.** `make release` depends on `release-check`; if any step fails, the release is blocked.
-**When VERSION changes:** (1) Regenerate `docs/demo.gif` with `make install && bash -c "vhs docs/demo.tape"` (run from repo root). The demo shows `pgwd -version`; the GIF must reflect the new version. See `docs/README.md` for VHS setup. (2) Update `contrib/man/man1/pgwd.1` — `.TH` line (date and version). See `.cursor/rules/man-page-sync.mdc`.
+**When VERSION changes** (same `chore/release-*` PR): (1) Regenerate `docs/demo.gif` with `make install && bash -c "vhs docs/demo.tape"` from repo root (`bash -c` so zsh does not break VHS). The demo shows `pgwd -version`. See `docs/README.md`. (2) Update `contrib/man/man1/pgwd.1` — `.TH` date and version. See `man-page-sync.mdc`. (3) **`make port-freebsd-sync`** and **`make port-openbsd-sync`** so this repo’s BSD port Makefiles match `VERSION`. Do **not** treat Bugzilla or ports@ as part of the release.
CI runs lint and tests. The Security workflow runs govulncheck and Grype on the image; both must pass (no `continue-on-error`). Do not push a release tag if CI or Security is red.
diff --git a/AGENTS.md b/AGENTS.md
index d8116d6..439075c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -36,16 +36,16 @@ Context and instructions for AI coding agents working on **pgwd** (Postgres Watc
## Git flow
-- **Branches:** Work on `develop`. `main` is production and is only updated from `develop` at release time (see `.cursor/rules/git-flow.mdc`).
+- **Branches:** No direct pushes to `develop` or `main`. Topic branch → **PR into `develop`** (green CI, merge, delete branch). Production: **PR `develop` → `main`**, then annotated **`v*`** tag on `main` (see `.cursor/rules/git-flow.mdc` and skill `pgwd-release`).
- **Commits:** Always show the proposed commit message and wait for user approval before running `git commit`. See `.cursor/rules/commit-message-review.mdc`.
- **Releases:** Before releasing: run **`make release-check`** (validates **`VERSION`** semver, then lint, test, **cover-check**, test-integration, test-e2e-kube, **`make docker-scan`**). All must pass — they are MANDATORY.
-- **Versioning:** Semantic versioning (MAJOR.MINOR.PATCH) for tags.
+- **Versioning:** Semantic versioning (MAJOR.MINOR.PATCH) for tags. The version cut is a **solo PR** into `develop` (`chore/release-X.Y.Z`): `VERSION`, CHANGELOG, README badge, **VHS** `docs/demo.gif`, man **`.TH`**, **`make port-freebsd-sync`** / **`port-openbsd-sync`**. Do not mix with other changes.
## Docker
- Build image with version info: `make docker-build` (passes VERSION, COMMIT, BUILDDATE; without it the binary reports `dev`/`unknown`). For **linux/amd64** only (e.g. push to a private registry from another arch): `make docker-buildx-amd64` (`pgwd:amd64` locally) or `make docker-buildx-amd64-push DOCKER_IMAGE=registry/repo:tag` after `docker login`.
- Build context is whitelisted via `.dockerignore`: only `go.mod`, `go.sum`, `cmd/`, and `internal/` are sent.
-- Dockerfile: multi-stage (Go 1.26.5 build; **distroless/static-debian13:nonroot** runtime), non-root user, no shell/OS packages (HTTPS via bundled CA certs in static image).
+- Dockerfile: multi-stage (Go 1.26.6 build; **distroless/static-debian13:nonroot** runtime), non-root user, no shell/OS packages (HTTPS via bundled CA certs in static image).
## Repository structure
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee6b0d1..66a325a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,19 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Releas
## [Unreleased]
+## [1.1.1] - 2026-08-31
+
+Security patch for operators pulling the published binary and GHCR image (Go stdlib + `x/net`). Remaining [plan-1.1.1](docs/plan-1.1.1.md) contract-repair work (daemon latch without store, SPEC, YAML examples) is not in this tag.
+
+### Security
+
+- **Go toolchain:** bump minimum/build Go to **1.26.6** (stdlib fixes: [GO-2026-6218](https://pkg.go.dev/vuln/GO-2026-6218), [GO-2026-6090](https://pkg.go.dev/vuln/GO-2026-6090), [GO-2026-6089](https://pkg.go.dev/vuln/GO-2026-6089), [GO-2026-6088](https://pkg.go.dev/vuln/GO-2026-6088), [GO-2026-5972](https://pkg.go.dev/vuln/GO-2026-5972), [GO-2026-5026](https://pkg.go.dev/vuln/GO-2026-5026)).
+- **Dependencies:** bump transitive `golang.org/x/net` to **v0.56.0** ([GO-2026-5942](https://pkg.go.dev/vuln/GO-2026-5942)) so Grype on the image no longer fails `--fail-on high`.
+
+### Docs
+
+- **README / ports:** version badge and install examples → **1.1.1**; FreeBSD/OpenBSD port Makefiles synced from `VERSION`; man `.TH` and `docs/demo.gif`.
+
## [1.1.0] - 2026-08-10
Incident hygiene for on-call: PagerDuty incident lifecycle and quieter threshold alerts by default.
@@ -528,7 +541,8 @@ Pre-1.0 security and operator polish: removes insecure Kubernetes password disco
---
-[Unreleased]: https://github.com/hrodrig/pgwd/compare/v1.1.0...HEAD
+[Unreleased]: https://github.com/hrodrig/pgwd/compare/v1.1.1...HEAD
+[1.1.1]: https://github.com/hrodrig/pgwd/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/hrodrig/pgwd/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/hrodrig/pgwd/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/hrodrig/pgwd/compare/v0.9.0...v1.0.0
diff --git a/Dockerfile b/Dockerfile
index e32ee32..18053ba 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
# Local / CI image: compile inside Docker (make docker-build, security workflow Grype scan).
# Release images: GoReleaser builds static binaries, then Dockerfile.release packages them (distroless).
-FROM golang:1.26.5-alpine AS build
+FROM golang:1.26.6-alpine AS build
ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILDDATE=unknown
diff --git a/README.md b/README.md
index d4367af..e80feba 100644
--- a/README.md
+++ b/README.md
@@ -10,12 +10,12 @@
Watch your PostgreSQL connections
-[](https://github.com/hrodrig/pgwd/releases)
+[](https://github.com/hrodrig/pgwd/releases)
[](https://github.com/hrodrig/pgwd/releases)
[](https://github.com/hrodrig/pgwd/actions)
[](https://codecov.io/gh/hrodrig/pgwd)
[](https://gghstats.hermesrodriguez.com/hrodrig/pgwd)
-[](https://go.dev/)
+[](https://go.dev/)
[](https://opensource.org/licenses/MIT)
[](https://pkg.go.dev/github.com/hrodrig/pgwd)
[](https://deps.dev/go/github.com%2Fhrodrig%2Fpgwd)
@@ -188,7 +188,7 @@ The ingest host is [collect.gghstats.com](https://collect.gghstats.com) (shared
```json
{
- "version": "1.1.0",
+ "version": "1.1.1",
"commit": "abc1234",
"build_date": "2026-07-18T12:00:00Z",
"hash": "a1b2c3d4e5f67890",
@@ -669,17 +669,17 @@ curl -sSL https://raw.githubusercontent.com/hrodrig/pgwd/main/scripts/install.sh
| Platform | Command |
|----------|---------|
| **Homebrew (macOS)** | `brew install hrodrig/pgwd/pgwd` |
-| **Debian/Ubuntu** | `wget -q -O /tmp/pgwd.deb https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.deb && sudo dpkg -i /tmp/pgwd.deb` |
-| **Fedora / RHEL / AlmaLinux / Rocky / Oracle Linux** | Same `.rpm`: `sudo dnf install https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.rpm` |
+| **Debian/Ubuntu** | `wget -q -O /tmp/pgwd.deb https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.deb && sudo dpkg -i /tmp/pgwd.deb` |
+| **Fedora / RHEL / AlmaLinux / Rocky / Oracle Linux** | Same `.rpm`: `sudo dnf install https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.rpm` |
| **OpenSUSE** | Same `.rpm` via zypper: see [OpenSUSE](#opensuse) |
-| **Alpine** | `wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.tar.gz \| tar -xzf - -C /usr/local/bin` — see [Alpine (OpenRC)](#alpine-linux-openrc) |
+| **Alpine** | `wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.tar.gz \| tar -xzf - -C /usr/local/bin` — see [Alpine (OpenRC)](#alpine-linux-openrc) |
| **OpenBSD** | tarball with rc.d: see [OpenBSD](#openbsd) |
| **FreeBSD** | port or tarball: see [FreeBSD](#freebsd) |
| **NetBSD** | tarball with rc.d: see [NetBSD](#netbsd) |
| **DragonFly BSD** | tarball with rc.d: see [DragonFly BSD](#dragonfly-bsd) |
| **illumos / Solaris** | tarball with SMF: see [Solaris](#solaris) |
-Replace `v1.1.0` and `amd64` with your desired version and arch (e.g. `arm64`). See [Releases](https://github.com/hrodrig/pgwd/releases) for all assets. If a tag is not published yet, build packages locally with `make snapshot` and install the `.rpm` / `.deb` from `dist/`. **AlmaLinux 8/9:** see [AlmaLinux](#almalinux).
+Replace `v1.1.1` and `amd64` with your desired version and arch (e.g. `arm64`). See [Releases](https://github.com/hrodrig/pgwd/releases) for all assets. If a tag is not published yet, build packages locally with `make snapshot` and install the `.rpm` / `.deb` from `dist/`. **AlmaLinux 8/9:** see [AlmaLinux](#almalinux).
**Pre-built binaries:** [Releases](https://github.com/hrodrig/pgwd/releases) provide binaries (tar.gz, zip), `.deb`, and `.rpm` packages for Linux, macOS, and Windows (amd64 and arm64). The `.deb` and `.rpm` packages include the man page (`man pgwd`) and install `/etc/pgwd/pgwd.conf` (edit before use). The `.rpm` is the same artifact for Fedora, RHEL, AlmaLinux, Rocky Linux, and Oracle Linux (`dnf`); **AlmaLinux** + **systemd** were validated (install, `pgwd -dry-run -interval 0`, `systemctl enable --now pgwd.service`).
@@ -696,11 +696,11 @@ make install
**FreeBSD:** `/usr/bin/make` is **BSD Make**; the repo ships a small **`Makefile`** stub that forwards to **`gmake`** (uses **`all`** + **`.DEFAULT`** with **`gmake $@`**, not **`$(.MAKE.CMDGOALS)`**, which can be empty under BSD Make). Install **`devel/gmake`** (`pkg install gmake`) and a **Go** toolchain on **`PATH`** (`pkg install go` — binary is usually **`/usr/local/bin/go`**). Then **`make build`** or **`gmake build`**. Linux, macOS, and CI use **GNU Make**, which reads **`GNUmakefile`** first.
-**Release (GitHub):** See [Release steps](#release-steps) below for the full workflow. Quick: from `main`, `git tag v1.1.0`, `make release`. Requires [goreleaser](https://goreleaser.com) (`brew install goreleaser`). For a local snapshot build without publishing: `make snapshot` (outputs to `dist/`).
+**Release (GitHub):** See [Release steps](#release-steps) below for the full workflow. Quick: from `main`, `git tag v1.1.1`, `make release`. Requires [goreleaser](https://goreleaser.com) (`brew install goreleaser`). For a local snapshot build without publishing: `make snapshot` (outputs to `dist/`).
### Release steps
-Example: releasing **v1.1.0**. Copy, adjust the version and token, then run.
+Example: releasing **v1.1.1**. Copy, adjust the version and token, then run.
**1. Prerequisites** (install once):
@@ -724,13 +724,13 @@ make release-check
**3. Update version** — edit `VERSION` and `CHANGELOG.md`:
```bash
-echo "1.1.0" > VERSION
-# Edit CHANGELOG.md: move [Unreleased] items into [1.1.0], update compare links
+echo "1.1.1" > VERSION
+# Edit CHANGELOG.md: move [Unreleased] items into [1.1.1], update compare links
# Regenerate docs/demo.gif so embedded version matches (from repo root):
make install && bash -c "vhs docs/demo.tape"
# Update contrib/man/man1/pgwd.1 — .TH date and version (see man-page-sync rule)
git add VERSION CHANGELOG.md README.md docs/demo.gif contrib/man/man1/pgwd.1 # README badge if needed
-git commit -m "Release 1.1.0"
+git commit -m "Release 1.1.1"
git push origin develop
```
@@ -742,8 +742,8 @@ git pull origin main
git merge develop
git push origin main
-git tag -a v1.1.0 -m "Release 1.1.0"
-git push origin v1.1.0
+git tag -a v1.1.1 -m "Release 1.1.1"
+git push origin v1.1.1
```
**5. Publish release** — tokens required:
@@ -776,7 +776,7 @@ From **v0.8.0**, GitHub Releases include **SPDX** and **CycloneDX** SBOMs and **
**Verify the container image** (replace the tag):
```bash
-cosign verify ghcr.io/hrodrig/pgwd:v1.1.0 \
+cosign verify ghcr.io/hrodrig/pgwd:v1.1.1 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github\.com/hrodrig/pgwd/\.github/workflows/release\.yml@refs/tags/v'
```
@@ -789,7 +789,7 @@ cosign verify-blob \
checksums.txt
```
-**Inspect SBOMs:** download `pgwd__sbom.spdx.json` or `pgwd__sbom.cyclonedx.json` from the release page, or scan with Grype: `grype sbom:pgwd_v1.1.0_sbom.spdx.json`.
+**Inspect SBOMs:** download `pgwd__sbom.spdx.json` or `pgwd__sbom.cyclonedx.json` from the release page, or scan with Grype: `grype sbom:pgwd_v1.1.1_sbom.spdx.json`.
Local `make release` does not sign artifacts — use the tag-push CI workflow for signed releases.
@@ -1029,12 +1029,12 @@ When you use `-db-threshold-levels 75,85,95` (default), pgwd fires one alert per
**Published image (each release):** Multi-arch images (linux/amd64, linux/arm64) are published to [GitHub Container Registry](https://github.com/hrodrig/pgwd/pkgs/container/pgwd) as `ghcr.io/hrodrig/pgwd`. Use a version tag or `latest`:
```bash
-docker pull ghcr.io/hrodrig/pgwd:v1.1.0
+docker pull ghcr.io/hrodrig/pgwd:v1.1.1
# or
docker pull ghcr.io/hrodrig/pgwd:latest
```
-**Build from source:** Multi-stage **Dockerfile** (Go 1.26.5 build; **distroless/static-debian13:nonroot** runtime): static binary, non-root, no Alpine OS packages. Use `make docker-build` locally; release images via GoReleaser + **`Dockerfile.release`** (same distroless base).
+**Build from source:** Multi-stage **Dockerfile** (Go 1.26.6 build; **distroless/static-debian13:nonroot** runtime): static binary, non-root, no Alpine OS packages. Use `make docker-build` locally; release images via GoReleaser + **`Dockerfile.release`** (same distroless base).
**Image details**
@@ -1055,13 +1055,13 @@ This runs `docker build` with `--build-arg VERSION=...`, `--build-arg COMMIT=...
**Validate the image**
-Use the published image `ghcr.io/hrodrig/pgwd:latest` (or `:v1.1.0`), or `pgwd` if you built locally with `make docker-build`:
+Use the published image `ghcr.io/hrodrig/pgwd:latest` (or `:v1.1.1`), or `pgwd` if you built locally with `make docker-build`:
```bash
# Help (no DB needed)
docker run --rm ghcr.io/hrodrig/pgwd:latest -h
-# Version (should show e.g. pgwd v1.1.0 (branch develop, commit ..., built ...))
+# Version (should show e.g. pgwd v1.1.1 (branch develop, commit ..., built ...))
docker run --rm ghcr.io/hrodrig/pgwd:latest --version
# Expect "missing database URL" (validates startup path)
@@ -1167,14 +1167,14 @@ Debian and Ubuntu use **systemd** and **`.deb`** packages. The same `.deb` works
**Install from GitHub** (replace version / arch):
```bash
-wget -q -O /tmp/pgwd.deb https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.deb
+wget -q -O /tmp/pgwd.deb https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.deb
sudo dpkg -i /tmp/pgwd.deb
```
**Local `.deb`** (e.g. from `make snapshot` → `dist/`):
```bash
-sudo dpkg -i ./pgwd_v1.1.0_linux_amd64.deb
+sudo dpkg -i ./pgwd_v1.1.1_linux_amd64.deb
```
**Configure and test**
@@ -1201,13 +1201,13 @@ sudo systemctl status pgwd.service
**Install from GitHub** (replace version / arch):
```bash
-sudo dnf install -y "https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.rpm"
+sudo dnf install -y "https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.rpm"
```
**Local `.rpm`** (e.g. from `make snapshot` → `dist/`, when a release is not on GitHub yet):
```bash
-sudo dnf install -y ./pgwd_v1.1.0_linux_amd64.rpm
+sudo dnf install -y ./pgwd_v1.1.1_linux_amd64.rpm
```
**Configure and test**
@@ -1233,13 +1233,13 @@ sudo systemctl status pgwd.service
```bash
sudo zypper --non-interactive install --allow-unsigned-rpm \
- "https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.rpm"
+ "https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.rpm"
```
**Local `.rpm`** (e.g. from `make snapshot` → `dist/`):
```bash
-sudo zypper --non-interactive install --allow-unsigned-rpm ./pgwd_v1.1.0_linux_amd64.rpm
+sudo zypper --non-interactive install --allow-unsigned-rpm ./pgwd_v1.1.1_linux_amd64.rpm
```
**Configure and test**
@@ -1263,10 +1263,10 @@ sudo systemctl status pgwd.service
Arch Linux uses **systemd**. There is no official **`pacman`** package in the Arch repos yet; install the **Linux release tarball** from [Releases](https://github.com/hrodrig/pgwd/releases) or a community **[AUR](https://aur.archlinux.org/)** package (e.g. `pgwd-bin`) when one exists — verify the PKGBUILD and checksums.
-**Tarball install** — extract the archive, then install the binary and config layout (replace `v1.1.0` / `amd64` as needed):
+**Tarball install** — extract the archive, then install the binary and config layout (replace `v1.1.1` / `amd64` as needed):
```bash
-wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.tar.gz | tar -xzf -
+wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.tar.gz | tar -xzf -
sudo install -Dm755 pgwd /usr/local/bin/pgwd
sudo ln -sf /usr/local/bin/pgwd /usr/bin/pgwd
sudo install -Dm644 share/man/man1/pgwd.1 /usr/local/share/man/man1/pgwd.1
@@ -1300,7 +1300,7 @@ Alpine uses **OpenRC** (rc.d), not systemd. Config: `/etc/pgwd/pgwd.conf`.
**Install** — tar.gz (binario estático, musl-compatible):
```bash
-wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.tar.gz | tar -xzf - -C /usr/local/bin
+wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.tar.gz | tar -xzf - -C /usr/local/bin
# arm64: replace amd64 with arm64
```
@@ -1336,7 +1336,7 @@ OpenBSD uses **rc.d**, not systemd. Config: `/etc/pgwd/pgwd.conf`. Supports `-ku
**Install** — tarball includes binary, rc.d script, and config example:
```bash
-tar xzf pgwd_v1.1.0_openbsd_amd64.tar.gz
+tar xzf pgwd_v1.1.1_openbsd_amd64.tar.gz
doas install -m755 pgwd /usr/local/bin/
doas install -m555 share/openbsd/rc.d/pgwd /etc/rc.d/pgwd
doas mkdir -p /etc/pgwd
@@ -1374,7 +1374,7 @@ make install
**Install from tarball** (or use the [one-liner](#install) which works on FreeBSD and installs only the binary):
```bash
-fetch -o /tmp/pgwd.tgz https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_freebsd_amd64.tar.gz
+fetch -o /tmp/pgwd.tgz https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_freebsd_amd64.tar.gz
tar -xzf /tmp/pgwd.tgz -C /tmp
sudo install -m755 /tmp/pgwd /usr/local/bin/
sudo mkdir -p /usr/local/etc/pgwd
@@ -1403,7 +1403,7 @@ NetBSD uses **rc.d**, not systemd. Config: `/etc/pgwd/pgwd.conf`. Supports `-kub
**Install** — tarball includes binary, rc.d script, and config example:
```bash
-tar xzf pgwd_v1.1.0_netbsd_amd64.tar.gz
+tar xzf pgwd_v1.1.1_netbsd_amd64.tar.gz
install -m755 pgwd /usr/local/bin/
install -m555 share/netbsd/rc.d/pgwd /etc/rc.d/pgwd
mkdir -p /etc/pgwd
@@ -1426,7 +1426,7 @@ See [contrib/netbsd/README.md](contrib/netbsd/README.md) for details.
**Install** — tarball includes binary, rc.d script, and config example:
```bash
-tar xzf pgwd_v1.1.0_dragonfly_amd64.tar.gz
+tar xzf pgwd_v1.1.1_dragonfly_amd64.tar.gz
install -m755 pgwd /usr/local/bin/
install -m555 share/dragonfly/rc.d/pgwd /etc/rc.d/pgwd
mkdir -p /etc/pgwd
@@ -1451,7 +1451,7 @@ See [contrib/dragonflybsd/README.md](contrib/dragonflybsd/README.md) for details
**Install** — tarball includes binary, SMF manifest, method script, and config example:
```bash
-curl -L -o /tmp/pgwd.tar.gz "https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_solaris_amd64.tar.gz"
+curl -L -o /tmp/pgwd.tar.gz "https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_solaris_amd64.tar.gz"
cd /tmp && tar xzf pgwd.tar.gz
pfexec mkdir -p /usr/local/bin /lib/svc/manifest/site /etc/pgwd
@@ -1476,7 +1476,7 @@ See [contrib/solaris/README.md](contrib/solaris/README.md) for details.
**Canonical roadmap:** **[ROADMAP.md](ROADMAP.md)** — current release, release bands (0.8 → 1.0), calendar, key decisions, document map.
-Summary: **v1.1.0** (PagerDuty dedup/resolve + threshold anti-spam latch). Stable API: **v1.0.0**. Behavior contract: [SPECIFICATIONS.md](SPECIFICATIONS.md). Shipped releases: [CHANGELOG.md](CHANGELOG.md).
+Summary: **v1.1.1** (Go 1.26.6 + `x/net` security patch). Stable API: **v1.0.0**. Behavior contract: [SPECIFICATIONS.md](SPECIFICATIONS.md). Shipped releases: [CHANGELOG.md](CHANGELOG.md).
| Band | Status | Plan |
|------|--------|------|
@@ -1484,9 +1484,10 @@ Summary: **v1.1.0** (PagerDuty dedup/resolve + threshold anti-spam latch). Stabl
| **1.0.0** | ✅ Ready Jul 2026 | [plan-1.0.x.md](docs/plan-1.0.x.md) · [CHANGELOG](CHANGELOG.md#100---2026-07-18) |
| **1.0.1** | ✅ Ready Aug 2026 | [CHANGELOG](CHANGELOG.md#101---2026-08-01) |
| **1.1.0** | ✅ Ready Aug 2026 | [plan-1.1.x.md](docs/plan-1.1.x.md) · [CHANGELOG](CHANGELOG.md#110---2026-08-10) |
+| **1.1.1** | ✅ Ready Aug 2026 | [CHANGELOG](CHANGELOG.md#111---2026-08-31) |
-Shipped history (0.4 – 1.1.0)
+Shipped history (0.4 – 1.1.1)
| Version | Target | Scope |
|---------|--------|-------|
@@ -1502,6 +1503,7 @@ Summary: **v1.1.0** (PagerDuty dedup/resolve + threshold anti-spam latch). Stabl
| **1.0.0** | Jul 2026 ✅ | Stable API; remove `db:` / total-active thresholds / notify-on-connect flag; exit 2/3; compare docs |
| **1.0.1** | Aug 2026 ✅ | `golang.org/x/text` security bump; README/man/ports docs |
| **1.1.0** | Aug 2026 ✅ | PagerDuty dedup/resolve; threshold alert latch (`repeat_while_firing`) |
+| **1.1.1** | Aug 2026 ✅ | Go 1.26.6; `golang.org/x/net` v0.56.0 (GO-2026-5942) |
diff --git a/ROADMAP.md b/ROADMAP.md
index 5b3549d..395967c 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,10 +1,10 @@
# pgwd roadmap
-**Current release:** [v1.1.0](VERSION) (ready on `develop`; tag from `main` after `make release-check`) · **Branch:** `develop`
+**Current release:** [v1.1.1](VERSION) · **Branch:** `develop` · **Next:** remaining [plan-1.1.1](docs/plan-1.1.1.md) contract repair (latch without store, SPEC, YAML), then [1.2.x](docs/plan-1.2.x.md) (hygiene)
-**Status (2026-08-10):** **v1.1.0** ready — incident hygiene (PagerDuty `dedup_key`/`resolve` + threshold anti-spam latch). Stable API remains **v1.0.0** (+ additive 1.1 keys). Distro packaging continues in **1.x** (not a hard tag gate).
+**Status (2026-08-31):** **v1.1.1** shipped (Go **1.26.6** + `golang.org/x/net` **v0.56.0**). **v1.1.0** incident hygiene remains. Leftover **plan-1.1.1** work (daemon latch without store, SPEC dry-run/Stats, YAML examples) is not in this tag. **1.2.x** planned — Dependabot, KnownFields, env honesty, HTTP timeouts, docs/packaging sync. Stable API remains **v1.0.0** (+ additive 1.1+ keys). Distro packaging continues in **1.x** (not a hard tag gate).
-This file is the **single roadmap index**. Shipped behavior: [SPECIFICATIONS.md](SPECIFICATIONS.md) (v1.0.0 contract; **v1.1.0** latch/PagerDuty). Shipped releases: [CHANGELOG.md](CHANGELOG.md). Implementation detail per band: [docs/plan-0.7.x.md](docs/plan-0.7.x.md) → [docs/plan-1.0.x.md](docs/plan-1.0.x.md) → [docs/plan-1.1.x.md](docs/plan-1.1.x.md).
+This file is the **single roadmap index**. Shipped behavior: [SPECIFICATIONS.md](SPECIFICATIONS.md). Shipped releases: [CHANGELOG.md](CHANGELOG.md). Implementation detail per band: [docs/plan-0.7.x.md](docs/plan-0.7.x.md) → … → [docs/plan-1.1.x.md](docs/plan-1.1.x.md) → [docs/plan-1.1.1.md](docs/plan-1.1.1.md) → [docs/plan-1.2.x.md](docs/plan-1.2.x.md).
---
@@ -23,7 +23,9 @@ flowchart LR
C --> D["0.9.0 ✅"]
D --> E["1.0.0 ✅"]
E --> F["1.0.1 ✅"]
- F --> G["1.1.0 ✅ ready"]
+ F --> G["1.1.0 ✅"]
+ G --> H["1.1.1 ✅"]
+ H --> I["1.2.0 planned"]
```
| Band | Status | Target | Theme | Plan |
@@ -34,7 +36,9 @@ flowchart LR
| **0.9.x** | ✅ Ready (v0.9.0) | Jul 2026 | Pre-1.0 polish, DISCOVER removal, profiles, `--strict`, collector, SPEC audit | [plan-0.9.x.md](docs/plan-0.9.x.md) · [CHANGELOG](CHANGELOG.md#090---2026-07-13) |
| **1.0.0** | ✅ Ready (v1.0.0) | Jul 2026 | Breaking stable API, compare docs, **start official distro packaging** | [plan-1.0.x.md](docs/plan-1.0.x.md) · [CHANGELOG](CHANGELOG.md#100---2026-07-18) |
| **1.0.1** | ✅ Shipped | Aug 2026 | `golang.org/x/text` security bump + docs | [CHANGELOG](CHANGELOG.md#101---2026-08-01) |
-| **1.1.x** | ✅ Ready (v1.1.0) | Aug 2026 | Incident hygiene: PagerDuty dedup/resolve + threshold anti-spam latch | [plan-1.1.x.md](docs/plan-1.1.x.md) · [CHANGELOG](CHANGELOG.md#110---2026-08-10) |
+| **1.1.x** | ✅ Shipped (v1.1.0) | Aug 2026 | Incident hygiene: PagerDuty dedup/resolve + threshold anti-spam latch | [plan-1.1.x.md](docs/plan-1.1.x.md) · [CHANGELOG](CHANGELOG.md#110---2026-08-10) |
+| **1.1.1** | ✅ Shipped | Aug 2026 | Security: Go 1.26.6 + `x/net` v0.56.0. Remaining latch/SPEC/YAML in [plan-1.1.1.md](docs/plan-1.1.1.md) | [CHANGELOG](CHANGELOG.md#111---2026-08-31) |
+| **1.2.x** | 📋 Planned | — | Hygiene: Dependabot, KnownFields, env honesty, HTTP timeouts, docs/packaging | [plan-1.2.x.md](docs/plan-1.2.x.md) |
**Suggested calendar** (from band plans — **slip OK**; 0.7.x started 2026-07-02):
@@ -46,7 +50,9 @@ flowchart LR
| Jul 13 | v0.9.0 ✅ |
| Jul 18 | v1.0.0 ✅ |
| Aug 1 | v1.0.1 ✅ |
-| Aug 10 | v1.1.0 ✅ ready (tag from `main` after gates) |
+| Aug 10 | v1.1.0 ✅ |
+| Aug 31 | v1.1.1 ✅ (security; leftover [plan-1.1.1.md](docs/plan-1.1.1.md) after this tag) |
+| After 1.1.1 | v1.2.0 planned ([plan-1.2.x.md](docs/plan-1.2.x.md)) |
Each band: design → implement → test → `make release-check` → docs → tag from `main`.
@@ -134,21 +140,31 @@ Acceptance timelines are external (reviewers, freeze windows) — **not** a hard
→ [plan-1.0.x.md](docs/plan-1.0.x.md)
-### 1.1.x — incident hygiene (on-call ready) ✅ (v1.1.0 ready)
-
-Theme from 2026-08-09 multi-auditor review: product gap is **sustained-outage noise**, not code craft.
+### 1.1.x — incident hygiene (on-call ready) ✅ (v1.1.0)
| Item | Notes |
|------|--------|
| **PagerDuty `dedup_key` + `resolve`** ✅ | Stable key per target/problem; resolution uses `event_action: resolve` (not `trigger`+`info`) |
-| **Threshold anti-spam latch** ✅ | Default: notify on transition / escalation / de-escalation only; escape hatch `notifications.repeat_while_firing` |
+| **Threshold anti-spam latch** ✅ | Filter + config shipped; **cross-tick without store** → [1.1.1](docs/plan-1.1.1.md) |
| **SPEC fix** ✅ | Known Limitations row corrected; latch + PagerDuty resolve documented |
-| **Doc drift (C5)** | Partial — man/example/SPEC done; OCI/nfpm one-liners may still say “Slack/Loki” only (follow-up) |
-
-**Not in 1.1.x:** Dependabot, `slog`, cli split, per-DB kube, new channels — see Post-1.0 / later minors.
+| **Doc drift (C5)** | Partial — OCI/nfpm one-liners → [1.2.x](docs/plan-1.2.x.md) |
→ [plan-1.1.x.md](docs/plan-1.1.x.md) · [CHANGELOG](CHANGELOG.md#110---2026-08-10)
+### 1.1.1 — security patch ✅ (v1.1.1) + leftover contract repair 📋
+
+**Shipped in v1.1.1:** Go 1.26.6; transitive `golang.org/x/net` v0.56.0.
+
+**Still in** [plan-1.1.1.md](docs/plan-1.1.1.md): daemon latch wiring without metrics store; SPEC dry-run + Stats self-exclude; `db:` / wrong keys in operator examples.
+
+→ [CHANGELOG](CHANGELOG.md#111---2026-08-31) · [plan-1.1.1.md](docs/plan-1.1.1.md)
+
+### 1.2.x — audit hygiene 📋
+
+Dependabot (`gomod` + Actions), `KnownFields`, honest env int parse, HTTP `ReadHeaderTimeout`, packaging/docs sync (five notifiers, distroless README).
+
+→ [plan-1.2.x.md](docs/plan-1.2.x.md)
+
---
## Shipped history (0.4 → 0.7.0)
@@ -202,15 +218,16 @@ See [SPECIFICATIONS.md §2](SPECIFICATIONS.md#2-scope).
## Post-1.0 (ideas, not committed)
-**Shipped:** [1.1.x incident hygiene](docs/plan-1.1.x.md) (dedup/resolve + latch) in **v1.1.0**.
+**Shipped:** [1.1.x incident hygiene](docs/plan-1.1.x.md) in **v1.1.0**; [v1.1.1](CHANGELOG.md#111---2026-08-31) Go/`x/net` security patch.
+
+**Scheduled:** leftover [1.1.1 contract repair](docs/plan-1.1.1.md) · [1.2.x hygiene](docs/plan-1.2.x.md) (Dependabot, KnownFields, timeouts, docs sync).
Still ideas (not scheduled):
- Per-database `kube.postgres` in `databases:`
- Additional Prometheus series or OpenMetrics (today: text exposition on HTTP `/metrics`)
- Discord, email, additional channels via same notifier pattern as 0.7.x
-- Structured logging (`slog`) — audit Band C
-- Dependabot/Renovate + pin CI tool versions — audit Band B
+- Structured logging (`slog`)
- Finish / expand **official distro** coverage if any 1.x submissions still pending (see [plan-1.0.x.md](docs/plan-1.0.x.md) § Distro packaging)
Track via GitHub issues; promote to a band plan when scheduled.
@@ -224,7 +241,7 @@ Track via GitHub issues; promote to a band plan when scheduled.
| **ROADMAP.md** (this file) | Where we are, where we go, band index |
| **[SPECIFICATIONS.md](SPECIFICATIONS.md)** | Observable behavior contract for **shipped** code (v1.0.0) |
| **[CHANGELOG.md](CHANGELOG.md)** | What actually shipped per version |
-| **[docs/plan-0.7.x.md](docs/plan-0.7.x.md) … [plan-1.1.x.md](docs/plan-1.1.x.md)** | Implementation checklists per band |
+| **[docs/plan-0.7.x.md](docs/plan-0.7.x.md) … [plan-1.1.x.md](docs/plan-1.1.x.md) · [plan-1.1.1.md](docs/plan-1.1.1.md) · [plan-1.2.x.md](docs/plan-1.2.x.md)** | Implementation checklists per band |
| **[docs/use-cases.md](docs/use-cases.md)** | Operator scenario matrix (single/multi DB, K8s, credentials) |
| **[docs/compare.md](docs/compare.md)** | pgwd vs postgres_exporter, pgwatch, hosted APM, cloud alarms, DIY cron |
| **[docs/kubernetes-passwords.md](docs/kubernetes-passwords.md)** | K8s credentials + DISCOVER migration |
diff --git a/SPECIFICATIONS.md b/SPECIFICATIONS.md
index bda95bf..c9c2153 100644
--- a/SPECIFICATIONS.md
+++ b/SPECIFICATIONS.md
@@ -532,14 +532,14 @@ Requires an active metrics store (sqlite.path or metrics_store.driver+dsn).
### Build
- Go module: `github.com/hrodrig/pgwd`
-- Minimum Go: 1.26.5 (as of 0.8.0)
+- Minimum Go: 1.26.6 (as of Unreleased / next patch)
- `make build`: reads `VERSION`, injects `Version`/`Commit`/`BuildDate`/`Branch` via ldflags
- `make install`: installs to `$GOBIN`
- Cross-compile: `make build-linux`, `make build-darwin`, `make build-windows`, `make build-all` (output in `dist/`)
### Docker
-- Multi-stage build: `golang:1.26.5-alpine` → `gcr.io/distroless/static-debian13:nonroot`
+- Multi-stage build: `golang:1.26.6-alpine` → `gcr.io/distroless/static-debian13:nonroot`
- **Static binary** (`CGO_ENABLED=0`); runtime image has **no shell, kubectl, or OS packages**
- **HTTPS notifiers** (Slack, Loki, PagerDuty, etc.): CA bundle included in distroless/static
- **Kubernetes in-container:** `-kube-postgres` / `-kube-loki` use **client-go** (port-forward, API calls). **No kubectl binary** — mount kubeconfig or use in-cluster ServiceAccount + RBAC. **`DISCOVER_MY_PASSWORD` / `pods/exec` removed in 0.9.x**; use Secret-backed DSN or `kube.password_from_secret`.
diff --git a/VERSION b/VERSION
index 9084fa2..524cb55 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.1.0
+1.1.1
diff --git a/contrib/freebsd/Makefile b/contrib/freebsd/Makefile
index 68946e1..0040104 100644
--- a/contrib/freebsd/Makefile
+++ b/contrib/freebsd/Makefile
@@ -1,5 +1,5 @@
PORTNAME= pgwd
-PORTVERSION= 1.1.0
+PORTVERSION= 1.1.1
DISTVERSIONPREFIX= v
CATEGORIES= sysutils
# Local test before release: copy the tarball into DISTDIR (see `make -V DISTDIR`
diff --git a/contrib/man/man1/pgwd.1 b/contrib/man/man1/pgwd.1
index ff7f452..dd0331f 100644
--- a/contrib/man/man1/pgwd.1
+++ b/contrib/man/man1/pgwd.1
@@ -1,6 +1,6 @@
.\" pgwd(1) - Postgres Watch Dog
.\" Manual page for pgwd
-.TH PGWD 1 "2026-08-10" "pgwd v1.1.0" "User Commands"
+.TH PGWD 1 "2026-08-31" "pgwd v1.1.1" "User Commands"
.SH NAME
pgwd \- monitor PostgreSQL connections and notify via Slack, Loki, PagerDuty, Teams, or generic webhook
.SH SYNOPSIS
diff --git a/contrib/openbsd/port/Makefile b/contrib/openbsd/port/Makefile
index 7c751c9..092081a 100644
--- a/contrib/openbsd/port/Makefile
+++ b/contrib/openbsd/port/Makefile
@@ -4,8 +4,8 @@
# Uses pledge() for security.
COMMENT = postgres connection watchdog with Slack/Loki alerts
-DISTNAME = pgwd_v1.1.0_openbsd_${MACHINE_ARCH:S/aarch64/arm64/}
-PKGNAME = pgwd-1.1.0
+DISTNAME = pgwd_v1.1.1_openbsd_${MACHINE_ARCH:S/aarch64/arm64/}
+PKGNAME = pgwd-1.1.1
CATEGORIES = sysutils
HOMEPAGE = https://github.com/hrodrig/pgwd
MAINTAINER = hrodrig@usb.ve
@@ -17,8 +17,8 @@ PERMIT_DISTFILES = Yes
# Local test before release: copy tarball to DISTDIR or use
# make fetch MASTER_SITES=file:///absolute/path/to/dir/
# See README.md "Test with a local tarball".
-MASTER_SITES = https://github.com/hrodrig/pgwd/releases/download/v1.1.0/
-DISTFILES = pgwd_v1.1.0_openbsd_${MACHINE_ARCH:S/aarch64/arm64/}.tar.gz
+MASTER_SITES = https://github.com/hrodrig/pgwd/releases/download/v1.1.1/
+DISTFILES = pgwd_v1.1.1_openbsd_${MACHINE_ARCH:S/aarch64/arm64/}.tar.gz
EXTRACT_SUFX = .tar.gz
ONLY_FOR_ARCHS = amd64 arm64 aarch64
diff --git a/docs/demo.gif b/docs/demo.gif
index f44c659..7491c9b 100644
Binary files a/docs/demo.gif and b/docs/demo.gif differ
diff --git a/docs/plan-1.1.1.md b/docs/plan-1.1.1.md
new file mode 100644
index 0000000..6509d41
--- /dev/null
+++ b/docs/plan-1.1.1.md
@@ -0,0 +1,184 @@
+# pgwd plan 1.1.1 — contract repair (patch)
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Roadmap index:** [ROADMAP.md](../ROADMAP.md) · **Band:** 1.1.1 (patch; restores promises of [plan-1.1.x.md](./plan-1.1.x.md) + Go toolchain)
+
+**Goal:** Make the v1.1.0 “quiet by default” latch work across daemon ticks **without** a metrics store; align SPEC with dry-run and connection counting; fix broken post-1.0 YAML examples; ship **Go 1.26.6** (already on `develop`).
+
+**Architecture:** (1) Daemon keeps per-target check runner state (`MakeRunFunc` closure / `memPrev`) across ticker intervals instead of creating and discarding a new closure every tick. (2) `postgres.Stats` excludes the monitor backend (`pid <> pg_backend_pid()`), matching `LongQueryCount`. (3) SPEC documents that `-dry-run` sends **no** outbound HTTP (including connect failure). (4) Operator-facing YAML examples use `databases:` and current key names only.
+
+**Tech Stack:** Go 1.26.6, existing `internal/cli`, `internal/run`, `internal/postgres`, `internal/store` (optional).
+
+**Baseline:** v1.1.0 on `main` / `develop` (+ commit bumping `go.mod` to 1.26.6) · **Previous band:** [plan-1.1.x.md](./plan-1.1.x.md) · **Target tag:** `v1.1.1` after `make release-check` on `main`
+
+---
+
+## Global Constraints
+
+- English only in code, comments, commits, docs (repo rule).
+- Work on `develop`; never commit features to `main`.
+- Show proposed commit message; wait for explicit user approval before `git commit`.
+- Cyclomatic complexity ≤ 14 (`gocyclo -over 14`); `gofmt -s`; `go vet ./...`.
+- Library coverage gate remains ≥ 80% (`make cover-check`).
+- No new direct dependencies.
+- No Dependabot / `KnownFields` / packaging one-liner sweep beyond broken YAML examples (those belong in [plan-1.2.x.md](./plan-1.2.x.md)).
+- Do not require metrics store for latch; store remains preferred when configured.
+
+---
+
+## Behavior contract (to land in SPECIFICATIONS.md)
+
+### Latch across daemon ticks (no store)
+
+| Mode | Previous firing state source |
+|------|------------------------------|
+| Metrics store configured | `LastStates(..., 1)` (unchanged; survives process restart) |
+| No store, **daemon** | In-memory state held for the lifetime of the daemon loop **per target** (survives ticker intervals) |
+| No store, one-shot | Single check; latch N/A across process runs |
+
+**Bug today:** `runOneTarget` invokes `MakeRunFunc(...)()` once and drops the closure; `runTickerLoop` calls `runOneTarget` every interval → `memPrev` always empty → `ApplyFiringRepeatFilter` never suppresses.
+
+**Fix:** Reuse the check runner (or an equivalent per-target `memPrev` + stable `MakeRunFunc`) across ticks. Pool lifecycle may be created once per target in daemon mode (preferred) or equivalent; one-shot behavior unchanged.
+
+### Dry-run and connect failure
+
+| Path | `-dry-run` |
+|------|------------|
+| Threshold / resolution / long_query / force-notification | No outbound HTTP (log `[dry-run] would send: …` where applicable) |
+| `connect_failure` / `too_many_clients` | **Also** no outbound HTTP |
+
+Remove SPEC/CHANGELOG claims that infrastructure failures “bypass dry-run”. Code in `NotifyConnectFailure` already returns early on `cfg.DryRun`.
+
+### Connection Stats
+
+- **Total / active / idle:** exclude the monitor’s own backend (`pid <> pg_backend_pid()`), as already documented for Total and as already implemented for long-query counts.
+
+### Docs / examples
+
+- No `db:` key in current operator examples (removed in v1.0; hard load error).
+- Slack YAML key is `notifications.slack.webhook` (not `webhook_url`).
+- HTTP health path key is `http.healthz_path` (not `health_path`).
+
+---
+
+## Out of scope (this band)
+
+- Dependabot / Renovate / pinning CI `@latest` tools → [plan-1.2.x.md](./plan-1.2.x.md)
+- `yaml.KnownFields(true)`, `envInt` fail-loud → 1.2.x
+- `ReadHeaderTimeout`, OCI/nfpm “five channels” one-liners → 1.2.x
+- `nokube` build tag, `slog`, new notifiers, per-DB kube
+
+---
+
+## File map
+
+| File | Role |
+|------|------|
+| `internal/cli/cli.go` | Daemon tick loop: reuse runner / pool per target |
+| `internal/run/run.go` | Unchanged filter logic if wiring fixed; adjust only if API needed |
+| `internal/cli/*_test.go` or dedicated daemon-path test | Two ticks, **no store**, same severity → no second threshold send |
+| `internal/postgres/stats.go` | Exclude `pg_backend_pid()` |
+| `internal/postgres/stats_*_test.go` | Cover self-exclusion (unit/mock or integration) |
+| `SPECIFICATIONS.md` | Latch in-memory wording; dry-run table; Stats |
+| `CHANGELOG.md` | `[1.1.1]` Security (Go) + Fixed |
+| `docs/kubernetes-passwords.md`, `contrib/k8s/README.md`, `contrib/netbsd/README.md`, `contrib/solaris/README.md` | Replace `db:` / wrong keys in “current” examples |
+| `VERSION`, README badge/install, `contrib/man/man1/pgwd.1`, BSD ports | 1.1.1 sync |
+| `ROADMAP.md` | Band status |
+| `docs/plan-1.1.x.md` | Pointer: in-memory cross-tick latch completed in 1.1.1 |
+
+---
+
+## Design notes (locked)
+
+1. **Prefer fix wiring over “require sqlite”.** SPEC already promises in-memory daemon state; requiring a store would be a product regression for minimal installs.
+2. **Existing `TestMakeRunFunc_*` that call `fn(); fn()` on one closure stay valid** but do **not** prove production wiring. Add a test that exercises the **daemon path** (or extracted helper used by `runTickerLoop`) without store.
+3. **Dry-run = zero HTTP** matches README “no HTTP calls” and the name dry-run; SPEC was wrong.
+4. **Go 1.26.6** may already be on `develop`; still document under `[1.1.1]` Security when tagging.
+
+---
+
+### Task 1: Failing test — latch without store across two daemon ticks
+
+**Files:**
+- Modify or create: test under `internal/cli` or `internal/run` that mirrors production wiring (new helper OK if it keeps `gocyclo` ≤ 14)
+
+**Want:**
+
+```text
+st == nil
+repeat_while_firing == false
+tick 1: threshold alert → Send called
+tick 2: same severity → Send NOT called for connection-threshold events
+```
+
+Must **fail** on current `runOneTarget` + `runTickerLoop` behavior (or fail until helper used by production is fixed).
+
+- [ ] **Step 1: Write failing test**
+- [ ] **Step 2: Run — expect FAIL**
+
+```bash
+go test ./internal/cli/ ./internal/run/ -count=1 -run Latch|Daemon|TwoTick
+```
+
+---
+
+### Task 2: Fix daemon wiring
+
+**Files:**
+- Modify: `internal/cli/cli.go` (`runTickerLoop` / `runOneTarget`)
+
+**Approach (pick one; prefer A):**
+
+- **A:** Per target, create pool once, `fn := run.MakeRunFunc(...)`, call `fn()` each tick; `defer` close pools on daemon exit.
+- **B:** Keep reconnect-per-tick but pass/update a shared `map[targetKey]*string` for `memPrev` into an extended `MakeRunFunc` / check entrypoint.
+
+- [ ] **Step 1: Implement**
+- [ ] **Step 2: Test PASS**
+- [ ] **Step 3: Commit** (after approval)
+
+---
+
+### Task 3: Stats exclude monitor PID
+
+**Files:**
+- Modify: `internal/postgres/stats.go`
+- Modify: unit/integration tests
+
+- [ ] **Step 1: Add `AND pid <> pg_backend_pid()` to Stats query**
+- [ ] **Step 2: Test + `go test ./internal/postgres/ -count=1`**
+- [ ] **Step 3: Commit** (after approval)
+
+---
+
+### Task 4: SPEC + CHANGELOG + YAML example sweep
+
+**Files:**
+- `SPECIFICATIONS.md` (dry-run, latch in-memory, Stats)
+- `CHANGELOG.md` → section `[1.1.1]`
+- Operator examples still showing `db:` / wrong Slack/health keys (SPEC §15 samples, kubernetes/kubernetes-passwords.md “after” examples, contrib READMEs listed in file map)
+
+- [ ] **Step 1: Align SPEC dry-run + latch wording**
+- [ ] **Step 2: Fix YAML examples to v1.0+ keys**
+- [ ] **Step 3: CHANGELOG `[1.1.1]` Fixed + Security (Go 1.26.6)**
+- [ ] **Step 4: Commit** (after approval)
+
+---
+
+### Task 5: Release prep 1.1.1
+
+- [ ] **Step 1:** `VERSION` → `1.1.1`; man `.TH`; README badge/install examples; FreeBSD/OpenBSD port `PORTVERSION` / equivalent
+- [ ] **Step 2:** Note in [plan-1.1.x.md](./plan-1.1.x.md); ROADMAP row **1.1.1**
+- [ ] **Step 3:** `make release-check` on `main` path per git-flow
+- [ ] **Step 4:** PR `develop` → `main`; annotated tag `v1.1.1`
+
+---
+
+## Success criteria
+
+- [ ] Daemon without metrics store does not re-notify same connection-threshold severity every interval
+- [ ] New test covers production wiring (not only reused closure)
+- [ ] `Stats` excludes monitor PID; SPEC matches
+- [ ] Dry-run documented as zero HTTP including connect failure
+- [ ] No `db:` in current operator copy-paste examples listed above
+- [ ] Tag `v1.1.1` with Go 1.26.6
diff --git a/docs/plan-1.1.x.md b/docs/plan-1.1.x.md
index e1b3ad8..da5ec4d 100644
--- a/docs/plan-1.1.x.md
+++ b/docs/plan-1.1.x.md
@@ -10,7 +10,7 @@
**Tech Stack:** Go 1.26.x, existing `internal/notify`, `internal/run`, `internal/store`, `httptest` for PD unit tests, SQLite metrics store for latch persistence across process restarts.
-**Source audits:** `.no-va-al-repo/20260809/` (Kimi, GLM, DeepSeek, MiniMax) — consensus items A1/A2/A3.
+**Follow-up:** In-memory latch across daemon ticks without a metrics store is completed in **[plan-1.1.1.md](./plan-1.1.1.md)** (v1.1.0 filter existed; production wiring discarded the closure each tick).
**Baseline:** v1.0.1 on `develop` · **Previous band:** [plan-1.0.x.md](./plan-1.0.x.md) · **Target tag:** v1.1.0 after `make release-check` on `main`
diff --git a/docs/plan-1.2.x.md b/docs/plan-1.2.x.md
new file mode 100644
index 0000000..d3bd1b2
--- /dev/null
+++ b/docs/plan-1.2.x.md
@@ -0,0 +1,146 @@
+# pgwd plan 1.2.x — audit hygiene (minor)
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Roadmap index:** [ROADMAP.md](../ROADMAP.md) · **Band:** 1.2.x (minor; process + config honesty + docs/packaging sync)
+
+**Goal:** Automate dependency and Actions updates; make config parsing fail honestly; harden optional HTTP server timeouts; sync public packaging/docs one-liners and man/README drift left after 1.1.x.
+
+**Architecture:** (1) GitHub Dependabot for `gomod` and `github-actions` with grouped PRs. (2) Strict YAML (`KnownFields`) and non-silent env int/duration parse. (3) Log store errors that today are discarded with `_` on hysteresis/resolution paths. (4) `http.Server` timeouts. (5) Docs/OCI/nfpm/Homebrew describe all five notifiers; README image details match distroless.
+
+**Tech Stack:** Existing Go module; GitHub Dependabot; no new runtime dependencies required.
+
+**Baseline:** v1.1.1 (contract repair) · **Previous band:** [plan-1.1.1.md](./plan-1.1.1.md) · **Target tag:** `v1.2.0` after `make release-check` on `main`
+
+---
+
+## Global Constraints
+
+- English only in code, comments, commits, docs (repo rule).
+- Work on `develop`; never commit features to `main`.
+- Show proposed commit message; wait for explicit user approval before `git commit`.
+- Cyclomatic complexity ≤ 14; `gofmt -s`; `go vet ./...`.
+- Library coverage gate ≥ 80% (`make cover-check`).
+- `KnownFields(true)` is **operator-visible**: configs with unknown YAML keys start failing load — document under CHANGELOG **Changed** and a short UPGRADE note or README tip.
+- Do not flip `enable_update_check` default in this band (separate product decision).
+- Do not add `nokube` build tag, `slog` migration, or new notifier channels.
+
+---
+
+## Behavior contract (to land in SPECIFICATIONS.md / docs)
+
+### Dependabot
+
+- Repo ships `.github/dependabot.yml` for:
+ - `package-ecosystem: gomod` (directory `/`)
+ - `package-ecosystem: github-actions` (directory `/`)
+- Weekly schedule; group Go module updates where Dependabot allows to reduce PR noise.
+
+### Config honesty
+
+| Input | Behavior after 1.2 |
+|-------|-------------------|
+| Unknown YAML keys | Load error (`KnownFields(true)`) |
+| `PGWD_*` int/duration set but unparseable | Do **not** silently use `0`; reject or keep previous/default with explicit log/error (prefer fail validation / skip apply with warning — pick one and lock in Task 2) |
+| `LastStates` / MaxConn / Stale query errors | Log at error or warn; do not fail open without signal |
+
+### HTTP server
+
+- When `http.listen` is set: `ReadHeaderTimeout` (and sensible `ReadTimeout` / `IdleTimeout`) on `http.Server`.
+
+### Public surface sync
+
+- OCI / nfpm / Homebrew descriptions list Slack, Loki, PagerDuty, Teams, and generic webhook (not “Slack/Loki” only).
+- README “Image details” describes distroless runtime (not Alpine BusyBox runtime).
+- Man page: CSV export not “SQLite only”; document `-notifications-repeat-while-firing` and other missing flags as needed.
+- README: remove obsolete `-kube-password-*` rows; add 1.1 latch flag to parameter table if still missing.
+- `docs/compare.md` snapshot version line → 1.2.0 era.
+
+---
+
+## Out of scope (this band)
+
+- Latch / Stats / dry-run contract fixes → [plan-1.1.1.md](./plan-1.1.1.md)
+- `nokube` binary split, Patroni, per-DB kube, Discord/email
+- Blocking `golangci-lint` (optional non-blocking job may be parked)
+- Query `statement_timeout` / `db.query_timeout` (nice-to-have; park unless XS leftover)
+- Marketing / co-maintainer process
+
+---
+
+## File map
+
+| File | Role |
+|------|------|
+| `.github/dependabot.yml` | **Create** |
+| `.github/workflows/*.yml`, `GNUmakefile` | Pin tool versions where `@latest` today |
+| `internal/config/file.go` | `yaml.NewDecoder` + `KnownFields(true)` |
+| `internal/config/config.go` | `envInt` / `envDuration` honesty |
+| `internal/config/*_test.go` | Unknown key fails; bad env behavior |
+| `internal/run/run.go` | Log discarded store/query errors |
+| `internal/httpsrv/server.go` | Server timeouts |
+| `Dockerfile`, `Dockerfile.release`, `.goreleaser.yaml` | Description strings |
+| `README.md`, `contrib/man/man1/pgwd.1`, `docs/compare.md` | Drift fixes |
+| `contrib/systemd/pgwd.service` | Optional XS hardening |
+| `SPECIFICATIONS.md`, `CHANGELOG.md`, `ROADMAP.md`, `VERSION` | 1.2.0 |
+
+---
+
+## Design notes (locked)
+
+1. **Dependabot over Renovate** for this band (native GitHub, one YAML file). Revisit Renovate only if grouping/noise becomes painful.
+2. **KnownFields** may break configs that carried typo keys or forward-compat junk — that is intended; document migration (“remove unknown keys”).
+3. **envInt:** returning `0` on parse failure is worse than default (turns interval into one-shot). Prefer: if env set and parse fails → log + keep `def`, **or** fail validation at startup when that key was required. Lock in Task 2 implementation notes.
+4. Pinning Actions by SHA is desirable; if too heavy for one PR, pin tool **install** versions in Makefile/CI first and leave Action SHA pinning as follow-up checkbox.
+
+---
+
+### Task 1: Dependabot + tool pins
+
+- [ ] **Step 1: Add `.github/dependabot.yml`** (`gomod` + `github-actions`, weekly, groups for gomod)
+- [ ] **Step 2: Replace `@latest` govulncheck/gocyclo (and document goreleaser-action version policy)**
+- [ ] **Step 3: Commit** (after approval)
+
+---
+
+### Task 2: KnownFields + env parse + store error logs
+
+- [ ] **Step 1: Failing tests** — unknown YAML key; `PGWD_INTERVAL=abc` does not become `0`
+- [ ] **Step 2: Implement decoder KnownFields + envInt/envDuration fix**
+- [ ] **Step 3: Log errors instead of `_` on LastStates / related paths in `internal/run`**
+- [ ] **Step 4: Tests PASS; commit** (after approval)
+
+---
+
+### Task 3: HTTP timeouts
+
+- [ ] **Step 1: Set `ReadHeaderTimeout` (e.g. 10s) on `http.Server` in `httpsrv`**
+- [ ] **Step 2: Test or smoke that server still starts; commit** (after approval)
+
+---
+
+### Task 4: Docs / packaging sync
+
+- [ ] **Step 1: OCI / goreleaser / Homebrew descriptions → five notifiers**
+- [ ] **Step 2: README image details → distroless; parameter table + drop dead kube-password flags**
+- [ ] **Step 3: Man page CSV + missing flags; compare.md snapshot**
+- [ ] **Step 4: Optional systemd hardening XS**
+- [ ] **Step 5: Commit** (after approval)
+
+---
+
+### Task 5: Release 1.2.0
+
+- [ ] **Step 1:** `VERSION` 1.2.0; CHANGELOG; SPEC notes; ROADMAP; man/badge/ports
+- [ ] **Step 2:** `make release-check`
+- [ ] **Step 3:** PR → `main`; tag `v1.2.0`
+
+---
+
+## Success criteria
+
+- [ ] Dependabot config merged; first PR cadence possible
+- [ ] Unknown YAML keys fail load; bad env ints do not silently become `0`
+- [ ] HTTP metrics server has header timeout
+- [ ] Public one-liners and README/man match distroless + five notifiers
+- [ ] Tag `v1.2.0`
diff --git a/go.mod b/go.mod
index 397dd56..c5c2e0f 100644
--- a/go.mod
+++ b/go.mod
@@ -1,13 +1,13 @@
module github.com/hrodrig/pgwd
-go 1.26.5
+go 1.26.6
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/go-sql-driver/mysql v1.8.1
github.com/jackc/pgx/v5 v5.9.2
github.com/ncruces/go-sqlite3 v0.32.0
- golang.org/x/sys v0.45.0
+ golang.org/x/sys v0.46.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.35.0
k8s.io/apimachinery v0.35.0
@@ -43,10 +43,10 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
- golang.org/x/net v0.55.0 // indirect
+ golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.21.0 // indirect
- golang.org/x/term v0.43.0 // indirect
+ golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.39.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
diff --git a/go.sum b/go.sum
index 9a19cb1..821d2b1 100644
--- a/go.sum
+++ b/go.sum
@@ -109,16 +109,16 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
-golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
-golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
+golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
+golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
-golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
-golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
-golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
-golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
+golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
+golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
+golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
diff --git a/testing/platforms/inventory/group_vars/all.yml b/testing/platforms/inventory/group_vars/all.yml
index 296af8c..d5b0338 100644
--- a/testing/platforms/inventory/group_vars/all.yml
+++ b/testing/platforms/inventory/group_vars/all.yml
@@ -2,6 +2,6 @@
# Shared defaults for all platform test hosts.
# Override in hosts.yml or per-host vars as needed.
-pgwd_version: "1.1.0"
+pgwd_version: "1.1.1"
pgwd_release_url: "https://github.com/hrodrig/pgwd/releases/download/v{{ pgwd_version }}"
notification_mock_port: 9999