Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
b14e999
:page_facing_up: docs(spec): re-measure cross-file-consistency-gates …
konih Oct 14, 2026
c2b744a
:white_check_mark: test(ci): add Go toolchain consistency guard (CFC-1)
konih Oct 14, 2026
8f7145d
:arrow_up: chore(deps): bump Go to 1.27.2 in go.mod and the devcontainer
konih Oct 14, 2026
d744da3
:wrench: build(deps): group the go directive and golang images in Ren…
konih Oct 14, 2026
aa9542a
:page_facing_up: docs: state Go 1.27.2 where the setup docs restate g…
konih Oct 14, 2026
a379dac
:page_facing_up: docs(spec): record G1 probes and evidence for the Go…
konih Oct 14, 2026
515c4c3
:white_check_mark: test(ci): close Go toolchain guard parsing gaps fo…
konih Oct 14, 2026
efaf493
:wrench: build(deps): make the Renovate go toolchain group the last g…
konih Oct 14, 2026
fc95930
:white_check_mark: test(ci): harden Go toolchain guard against round-…
konih Oct 14, 2026
a2b4041
:page_facing_up: docs(spec): record G1 state and review legs for cros…
konih Oct 14, 2026
093cd47
:page_facing_up: docs(spec): make CFC-2 catch analysis tools too old …
konih Oct 15, 2026
fa9bc99
:arrow_up: chore(deps): update golangci-lint to v2.14.0
konih Oct 15, 2026
9d38f74
:arrow_up: chore(deps): update go-arch-lint to v1.19.0
konih Oct 15, 2026
812c7f2
:arrow_up: chore(deps): update govulncheck to v1.9.0
konih Oct 15, 2026
00274b0
:page_facing_up: docs(spec): record G1 task 1.3 completion after the …
konih Oct 15, 2026
1e70579
:page_facing_up: docs(spec): record the G1 tool-pin review and close …
konih Oct 15, 2026
527cc41
:page_facing_up: docs(spec): record G1 closure for cross-file-consist…
konih Oct 15, 2026
13b263c
:white_check_mark: test(ci): guard tool pins against go.mod's Go
konih Oct 15, 2026
f62e76e
:bug: fix(ci): close round-1 gaps in the tool-pin guard
konih Oct 15, 2026
b564d1f
:bug: fix(ci): read continued and quoted tool pins; close G2
konih Oct 15, 2026
c04a0ea
:page_facing_up: docs(spec): pin CFC-2 to exactly the go.mod Go and r…
konih Oct 15, 2026
8178695
:page_facing_up: docs(spec): record RBAC probe for cross-file consist…
konih Oct 15, 2026
756360c
:page_facing_up: docs(spec): exempt team-granted workload reads from …
konih Oct 15, 2026
913a08f
:white_check_mark: test(ci): guard chart RBAC against the kustomize role
konih Oct 15, 2026
f3489c0
:bug: fix(ci): scope RBAC coverage by role kind and close team-workload
konih Oct 15, 2026
1b5b7b7
:bug: fix(ci): reject bare-class RBAC exceptions; close G3
konih Oct 15, 2026
369d597
:page_facing_up: docs(spec): name selfsubjectaccessreviews among the …
konih Oct 15, 2026
fba3eef
:sparkles: feat(ci): add a weekly Go patch-lag sensor
konih Oct 15, 2026
65a1dff
:page_facing_up: docs(spec): record G4 for cross-file-consistency-gates
konih Oct 15, 2026
0dbc630
:page_facing_up: docs(metrics): document the five undocumented operat…
konih Oct 15, 2026
05842fe
:white_check_mark: test(metrics): add the metric-name consistency guard
konih Oct 15, 2026
6fad36e
:bug: fix(test): read metric names as whole tokens in the metrics guard
konih Oct 15, 2026
dcf2be2
:bug: fix(test): fail on non-literal mirror entries in the metrics guard
konih Oct 15, 2026
c47170e
:page_facing_up: docs(spec): record G5 for cross-file-consistency-gates
konih Oct 15, 2026
3008019
:construction_worker: ci(lint): wire the cross-file consistency gates
konih Oct 15, 2026
3915645
:white_check_mark: test(ci): lock CFC guard wiring and the patch-lag …
konih Oct 15, 2026
366dc08
:white_check_mark: test(ci): require both CFC modes and count curl calls
konih Oct 15, 2026
d770c56
:construction_worker: ci: run the tool-pins self-test in its own job
konih Oct 15, 2026
b94b690
:white_check_mark: test(ci): give the tool-pins fixture module a per-…
konih Oct 15, 2026
2a9b5b9
:page_facing_up: docs(spec): record G6 decision A review for cross-fi…
konih Oct 15, 2026
1578f3a
:page_facing_up: docs(spec): record G6 and close the cross-file-consi…
konih Oct 15, 2026
b18354a
:page_facing_up: docs(spec): record stage-B review of cross-file-cons…
konih Oct 15, 2026
028bbc4
:page_facing_up: docs(spec): triage stage-B round 1 for cross-file-co…
konih Oct 15, 2026
a3880bc
:page_facing_up: docs(spec): fix markdownlint in cross-file-consisten…
konih Oct 15, 2026
d8dfe0c
:white_check_mark: test(ci): reject variable FROM images in the Go to…
konih Oct 15, 2026
dfbf33a
:bug: fix(ci): fail cleanly on an exception class without its colon
konih Oct 15, 2026
b95d2fe
:white_check_mark: test(metrics): compare Register() with registeredM…
konih Oct 15, 2026
6dd8562
:white_check_mark: test(ci): catch a variable image behind a registry…
konih Oct 15, 2026
7fe5b77
:white_check_mark: test(metrics): prove Register() fills controller-r…
konih Oct 15, 2026
9203283
:white_check_mark: test(ci): reject any variable in a FROM image
konih Oct 15, 2026
7d5a4d7
:page_facing_up: docs(spec): record G7 stage-B round-1 fixes for cros…
konih Oct 15, 2026
800a33e
:page_facing_up: docs(spec): record G7 stage-B fixes for cross-file-c…
konih Oct 15, 2026
aa22415
:page_facing_up: docs(spec): record stage-B round 2 of cross-file-con…
konih Oct 15, 2026
0fa52b2
:page_facing_up: docs(spec): triage stage-B round 2 and fix the loop.…
konih Oct 15, 2026
d4b9438
:bug: fix(ci): fail a golang FROM image that holds a variable
konih Oct 16, 2026
4409d94
:bug: fix(ci): parse comment and blank lines in a FROM continuation l…
konih Oct 16, 2026
5d01c4a
:white_check_mark: test(ci): record G8 evidence and fill the verifica…
konih Oct 16, 2026
120386b
:page_facing_up: docs(spec): record G8 and the hand-off notes for cro…
konih Oct 16, 2026
7fe45f6
:page_facing_up: docs(spec): add the PR description for cross-file-co…
konih Oct 16, 2026
4a367e2
:recycle: refactor(spec): keep evidence probes as Markdown
konih Oct 16, 2026
852709f
:white_check_mark: test(ci): guard openspec evidence as Markdown only
konih Oct 16, 2026
b73d730
:construction_worker: ci: run the evidence guard in lint
konih Oct 16, 2026
53eec18
:bug: fix(test): name the copy failure in the evidence guard self-test
konih Oct 16, 2026
86081a2
:white_check_mark: test(ci): require the evidence guard in both lint …
konih Oct 16, 2026
0830f95
:bug: fix(test): stage evidence-guard fixtures past global ignores
konih Oct 16, 2026
a0b214e
:white_check_mark: test(ci): keep the evidence guard in the CFC-6 list
konih Oct 16, 2026
ac5711a
:page_facing_up: docs(spec): record G9 and its reviews for cross-file…
konih Oct 16, 2026
5e7aa4a
:page_facing_up: docs(spec): record the Sonar evidence fix for cross-…
konih Oct 16, 2026
361a00e
:page_facing_up: docs(spec): note the evidence guard in the PR descri…
konih Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Kubebuilder DevContainer",
"image": "golang:1.26.9@sha256:d7722066f0b60ceccb6c0643cbed1f5f9e15506ac237146c95333504d7805d89",
"image": "golang:1.27.2@sha256:5bc7f572bbaa98885a3a1fd9c0aa76b59e3e14e8628bfc316bbfd0c701e4818c",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"moby": false,
Expand Down
61 changes: 60 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
# no required context is at stake. Locked by hack/test/ci_docs_gate_test.sh.
#
# The docs-only path is NOT free, and that is a deliberate trade. Where this workflow previously
# was not dispatched at all, a documentation-only PR now runs `changes`, `gitleaks` and `lint` --
# was not dispatched at all, a documentation-only PR now runs `changes`, `gitleaks`, `lint` and
# `tool-pins-selftest` (CFC-6, the tool-pins gate's self-test, split out of lint for wall time) --
# `lint` because two existing gates (dist_ci_wiring_test.sh, repo_root_links_wiring_test.sh)
# require it to be unconditional, and because it is where ci_docs_gate_test.sh runs, so leaving
# it ungated is the only thing that exercises the docs-only direction at all. What such a PR no
Expand Down Expand Up @@ -539,9 +540,67 @@ jobs:
bash "${script}"
echo "::endgroup::"
done
# CFC-1..CFC-6 (openspec change cross-file-consistency-gates): the cross-file consistency
# gates -- the Go toolchain agrees across go.mod, the Dockerfiles and the devcontainer
# (CFC-1), every pinned tool resolves and its analysis tools run under go.mod's Go (CFC-2),
# the chart's RBAC covers config/rbac/role.yaml in both install modes (CFC-3), registered
# metrics match the docs and rules (CFC-4), and the go-patch-lag sensor keeps its verdicts
# and its workflow shape (CFC-5). Plain and --self-test are separate steps (CFC-6): the
# first reds when the tree drifts, the second when a gate can no longer fail. They run here,
# unconditionally, because lint is a required context that also runs on docs-only PRs.
# Tools: jq (runner image), yq, helm and PyYAML installed above; Go from go.mod through
# go-cache (the tool-pins gate forces GOTOOLCHAIN=go<go.mod version> itself).
- name: Verify the Go toolchain agrees across go.mod, images and devcontainer (CFC-1)
run: bash hack/test/consistency_go_toolchain_test.sh
- name: Verify the Go toolchain gate can still fail (CFC-6)
run: bash hack/test/consistency_go_toolchain_test.sh --self-test
- name: Verify the chart RBAC covers role.yaml in both install modes (CFC-3)
run: bash hack/test/consistency_rbac_test.sh
- name: Verify the chart RBAC gate can still fail (CFC-6)
run: bash hack/test/consistency_rbac_test.sh --self-test
- name: Verify registered metrics match the docs and alert rules (CFC-4)
run: bash hack/test/consistency_metrics_test.sh
- name: Verify the metrics gate can still fail (CFC-6)
run: bash hack/test/consistency_metrics_test.sh --self-test
- name: Verify the go-patch-lag sensor verdicts and workflow shape (CFC-5)
run: bash hack/test/go_patch_lag_test.sh
- name: Verify the go-patch-lag test can still fail (CFC-6)
run: bash hack/test/go_patch_lag_test.sh --self-test
# Evidence under openspec/changes/**/evidence/ (archive included) is Markdown only: a probe
# script committed there is analysed by SonarCloud as uncovered new code although CI never
# runs it (decision 2026-10-16: inline probes in the evidence .md instead of widening
# sonar.exclusions). Plain and --self-test as separate steps, like the CFC gates above.
- name: Verify openspec evidence is Markdown only
run: bash hack/test/openspec_evidence_markdown_only_test.sh
- name: Verify the openspec evidence guard can still fail
run: bash hack/test/openspec_evidence_markdown_only_test.sh --self-test
# Network: the module proxy, golangci-lint's custom build and the vuln DB (about 30 s warm).
- name: Verify every pinned tool resolves and runs under go.mod's Go (CFC-2)
run: bash hack/test/consistency_tool_pins_test.sh
- name: Lint
run: task lint

# CFC-6 for CFC-2 (openspec change cross-file-consistency-gates): the tool-pins gate's
# --self-test builds each pinned analysis tool at its own and at a too-old version (about
# 7 minutes warm), so it runs in its own job, in parallel with lint, instead of doubling
# lint's wall time (operator decision 2026-10-16). Like lint it is NOT gated on the changes
# verdict: it runs on every PR, docs-only included, and is a required context
# (hack/release/verify-eligibility.sh). hack/test/ci_workflow_security_test.sh (CFC-6 rule)
# requires the self-test in this job and hack/test/ci_docs_gate_test.sh keeps the job
# unconditional. The timeout bounds a stalled module or vuln-DB download; local runs took
# 7 to 17 minutes.
tool-pins-selftest:
name: tool-pins-selftest
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/go-cache
- name: Verify the tool-pins gate can still fail (CFC-6)
run: bash hack/test/consistency_tool_pins_test.sh --self-test

# CI-DOCSGATE-01: the REAL suite. It is deliberately NOT named `test` -- the required context
# of that name is produced by the `test` job at the end of this file, which reports on every
# PR. A job that legitimately skips must never carry a required context name; that is exactly
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/go-patch-lag.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# CFC-5 (openspec change cross-file-consistency-gates): red when go.mod's Go is behind the latest
# patch of its minor, or when that patch cannot be determined (the lookup fails closed). A Go patch
# carries stdlib security fixes; this makes the lag visible before govulncheck does.
# Pinned by hack/test/go_patch_lag_test.sh (triggers, permissions, no secrets, one plain step,
# curl's retry budget inside the step's timeout-minutes).
name: Go patch lag

on:
schedule:
- cron: "23 6 * * 1"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: go-patch-lag-${{ github.ref }}
cancel-in-progress: false

jobs:
go-patch-lag:
name: go-patch-lag
runs-on: ubuntu-latest
# why: above the sensor step's own timeout, so checkout and runner setup cannot end the job
# before the step does.
timeout-minutes: 6
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Compare go.mod's Go with the latest patch of its minor
# why: curl's retry budget in the script (--retry-max-time + --max-time) must end inside
# this step's own timeout so a stalled go.dev fails with the script's message.
timeout-minutes: 5
run: bash hack/ci/go-patch-lag.sh
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ ENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \
[ -n "$$v" ] || { echo "Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)" >&2; exit 1; }; \
printf '%s\n' "$$v" | sed -E 's/^v?[0-9]+\.([0-9]+).*/1.\1/')

GOLANGCI_LINT_VERSION ?= v2.13.1
GOLANGCI_LINT_VERSION ?= v2.14.0
.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
$(KUSTOMIZE): $(LOCALBIN)
Expand Down
4 changes: 2 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ vars:
GO_ARCH_LINT_GRAPH: docs/architecture-graph.svg
MARKDOWNLINT_CONFIG: hack/tooling/markdownlint-cli2.yaml
# renovate: datasource=github-releases depName=fe3dback/go-arch-lint
GO_ARCH_LINT_VERSION: v1.15.0
GO_ARCH_LINT_VERSION: v1.19.0
# renovate: datasource=github-releases depName=helm/helm
HELM_VERSION: v3.22.0
# renovate: datasource=github-releases depName=norwoodj/helm-docs
Expand Down Expand Up @@ -201,7 +201,7 @@ tasks:
desc: Scan for known Go vulnerabilities (govulncheck)
deps: [install]
cmds:
- go run golang.org/x/vuln/cmd/govulncheck@v1.1.4 ./...
- go run golang.org/x/vuln/cmd/govulncheck@v1.9.0 ./...

test:
desc: Run unit tests with envtest (alias for test:run)
Expand Down
2 changes: 1 addition & 1 deletion docs/development/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Kubernetes cluster (typically [kind](https://kind.sigs.k8s.io/)).

| Tool | Version / notes |
| --- | --- |
| [Go](https://go.dev/dl/) | **1.26.9+** (see `go.mod`) |
| [Go](https://go.dev/dl/) | **1.27.2+** (see `go.mod`) |
| [Docker](https://docs.docker.com/get-docker/) | For container image builds and kind |
| [kubectl](https://kubernetes.io/docs/tasks/tools/) | 1.28+ recommended |
| [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) | Local cluster for smoke / e2e |
Expand Down
8 changes: 7 additions & 1 deletion docs/operator-manual/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ All custom metrics use the `kollect_` prefix. Controller-runtime also exposes st
| `kollect_reconcile_errors_total` | counter | `kind`, `error_class` | Errors by CR kind and class (`transient`, `terminal`, `forbidden`) |
| `kollect_reconcile_duration_seconds` | histogram | `controller` | Reconcile latency |
| `kollect_workqueue_depth` | gauge | `controller` | In-flight reconciles (approximate depth) |
| `kollect_watch_map_list_errors_total` | counter | `controller`, `watch` | Secondary watch map handlers that failed to list related objects (RBAC or API list failures) |
| `kollect_static_ref_resolution_total` | counter | `kind`, `ref_type`, `result` | Cluster-kind namespaced static-ref resolutions (`ok` / `not_found` / `forbidden`, [ADR-0208](../adr/0208-cluster-static-refs-via-namespace.md)) |
| `kollect_access_cache_total` | counter | `result` | SelfSubjectAccessReview access-cache outcomes: `hit` serves a cached decision, `miss` issues a fresh review |

### Collection

Expand All @@ -118,6 +121,8 @@ All custom metrics use the `kollect_` prefix. Controller-runtime also exposes st
| `kollect_export_bytes_total` | counter | `sink_type` | Payload bytes exported |
| `kollect_sink_errors_total` | counter | `reason` | Export failures (`transient`, `terminal`, `forbidden`, `payload_too_large`, `spill_required`, …) |
| `kollect_export_spill_warn_total` | counter | — | Payloads at/above 1 MiB spill warn threshold |
| `kollect_export_shard_warn_total` | counter | — | Inventory namespace aggregates at/above the export sharding warn row threshold (~1800 rows) |
| `kollect_namespace_fingerprint_cache_total` | counter | `controller`, `result` | Namespace content fingerprint cache outcomes: `hit` skips the snapshot + fingerprint recompute, `miss` pays for it |
| `kollect_sink_connection_test_total` | counter | `type`, `result` | Git/TLS connection tests |
| `kollect_cleanup_terminal_total` | counter | `kind` | Terminal sink-cleanup attempts on deleting objects (`inventory`, `cluster-inventory`). The cleanup finalizer is retained and the attempt re-checks every 5 minutes, so one wedged object increments the counter once per re-check — alert on any increase, not on the count; see [Troubleshooting](troubleshooting.md) |

Expand Down Expand Up @@ -157,7 +162,8 @@ sum(increase(kollect_sink_errors_total[15m])) by (reason)
kollect_collect_items_total
```

Source of truth for registration: `internal/metrics/metrics.go` and `internal/metrics/metrics_catalog.go`.
Source of truth for registration: `internal/metrics/metrics.go` and `internal/metrics/aggregation*.go`.
`internal/metrics/metrics_catalog.go` documents the same names for agents; it registers nothing.

## See also

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/platformrelay/kollect

go 1.26.9
go 1.27.2

require (
cloud.google.com/go/bigquery v1.85.0
Expand Down
1 change: 1 addition & 0 deletions hack/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ run_gate workflow-security-audit ./bin/zizmor --offline --no-progress --min-seve
# TCE-3 mapping: required contexts whose local equivalent is a combination of the gates above.
# hack/test/task_check_test.sh reads these lines; guard-sweep is the entry for the guard run.
# coverage: preflight=lint:markdown,go-mod,verify,guard-sweep
# coverage: tool-pins-selftest=guard-sweep


# The guard sweep: every hack/test guard, bare, plus --self-test for the scripts that parse
Expand Down
118 changes: 118 additions & 0 deletions hack/ci/go-patch-lag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# CFC-5 (openspec change cross-file-consistency-gates): is go.mod's Go behind the latest patch of
# its minor?
#
# Reads the `go` directive of go.mod and the go.dev release listing, and compares the directive
# with the newest STABLE release of the same minor. A newer minor existing is reported as a note,
# never as a failure. The lookup fails closed: an unreachable or unparsable listing, a minor the
# listing does not carry, or a go.mod newer than the listing is a red run, never a green one.
#
# `&include=all` is required: the default listing carries only the two newest minors, so a go.mod
# two minors behind would drop out of it and "behind" would read as "unknown".
#
# Exit codes: 0 current, 1 behind, 2 cannot determine (lookup failed or go.mod unreadable).
#
# Usage:
# hack/ci/go-patch-lag.sh [--gomod <go.mod>] [--listing <file> | --url <url>] [--retries <n>]
# --gomod go.mod to read (default: the repository's)
# --listing read the listing from a file instead of fetching it (tests)
# --url listing URL (default: https://go.dev/dl/?mode=json&include=all)
# --retries fetch retries (default 3; tests pass 0)
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
GOMOD="${ROOT}/go.mod"
URL="https://go.dev/dl/?mode=json&include=all"
LISTING=""
RETRIES=3

while [[ $# -gt 0 ]]; do
case "$1" in
--gomod)
GOMOD="${2:?--gomod needs a path}"
shift 2
;;
--listing)
LISTING="${2:?--listing needs a path}"
shift 2
;;
--url)
URL="${2:?--url needs a URL}"
shift 2
;;
--retries)
RETRIES="${2:?--retries needs a number}"
[[ "${RETRIES}" =~ ^[0-9]+$ ]] || { echo "go-patch-lag: --retries needs a number" >&2; exit 2; }
shift 2
;;
*)
echo "go-patch-lag: unknown argument '$1'" >&2
exit 2
;;
esac
done

minor_tag="?"
undetermined() { echo "go-patch-lag: FAIL: cannot determine the latest go${minor_tag} patch: $*" >&2; exit 2; }

command -v jq >/dev/null 2>&1 || undetermined "jq not found"

# --- go.mod's directive -------------------------------------------------------------------------
directive=""
if [[ -f "${GOMOD}" ]]; then
directive="$(sed -nE 's/^go[[:space:]]+([^[:space:]]+)[[:space:]]*$/\1/p' "${GOMOD}")"
fi
# Canonical numbers of at most four digits: bash arithmetic reads 08 as bad octal (an arithmetic
# error inside `if` is just "false") and wraps past 2^63 -- either way a false green.
num='(0|[1-9][0-9]{0,3})'
if [[ ! "${directive}" =~ ^${num}\.${num}\.${num}$ ]]; then
echo "go-patch-lag: FAIL: cannot read a go <major>.<minor>.<patch> directive from ${GOMOD} (found '${directive}')" >&2
exit 2
fi
minor_tag="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
patch="${BASH_REMATCH[3]}"

# --- the listing --------------------------------------------------------------------------------
if [[ -z "${LISTING}" ]]; then
LISTING="$(mktemp)"
trap 'rm -f "${LISTING:?}"' EXIT
# Bounded well inside the workflow step's 5-minute timeout, so a stalled go.dev ends with this
# script's own message, not a killed step: <= 200 s of retries plus one 45 s attempt.
# hack/test/go_patch_lag_test.sh fails when --retry-max-time + --max-time outgrow that timeout.
curl -fsSL --proto '=https' --tlsv1.2 --connect-timeout 10 --max-time 45 \
--retry "${RETRIES}" --retry-all-errors --retry-max-time 200 -o "${LISTING}" "${URL}" ||
undetermined "fetching ${URL} failed"
fi

# Stable patch numbers of go.mod's minor, one per line; jq errors on anything but a JSON array.
# shellcheck disable=SC2016 # $p is a jq variable
jq_program='if type != "array" then error("not an array") else . end
| .[] | select(type == "object") | select(.stable == true)
| .version | select(type == "string") | select(startswith($p)) | ltrimstr($p)
| select(test("^(0|[1-9][0-9]{0,3})$"))'
patches="$(jq -r --arg p "go${minor_tag}." "${jq_program}" "${LISTING}" 2>/dev/null)" ||
undetermined "the listing is not a JSON array of releases"
latest="$(sort -n <<<"${patches}" | tail -n 1)"
[[ -n "${latest}" ]] || undetermined "the listing has no stable go${minor_tag}.N release"

# The newest stable release overall, for the newer-minor note only.
newest="$(jq -r '[.[] | select(type == "object") | select(.stable == true) | .version
| select(type == "string") | capture("^go(?<a>[0-9]+)\\.(?<b>[0-9]+)\\.(?<c>[0-9]+)$")
| [(.a | tonumber), (.b | tonumber), (.c | tonumber)]] | max // empty
| "go\(.[0]).\(.[1]).\(.[2])"' "${LISTING}" 2>/dev/null || true)"

if ((10#${patch} > 10#${latest})); then
undetermined "go.mod's go ${directive} is newer than go${minor_tag}.${latest}, the newest listed -- the listing is stale or go.mod names an unreleased Go"
fi

behind() {
echo "go-patch-lag: FAIL: go.mod's go ${directive} is behind go${minor_tag}.${latest}, the latest go${minor_tag} patch -- bump go.mod (and the golang images, CFC-1)" >&2
exit 1
}
if ((10#${patch} < 10#${latest})); then
behind
fi
echo "go-patch-lag: ok: go.mod's go ${directive} is the latest go${minor_tag} patch"
if [[ -n "${newest}" && "${newest}" != "go${directive}" ]]; then
echo "go-patch-lag: note: a newer minor exists (${newest}); not a failure on its own"
fi
2 changes: 1 addition & 1 deletion hack/release/test-verify-eligibility.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ trap 'rm -rf "${TMP}"' EXIT
SHA="1111111111111111111111111111111111111111"
MAIN="2222222222222222222222222222222222222222"

REQUIRED_NAMES='gitleaks verify audit-rbac vulncheck lint test build test-integration helm docker-build preflight kind-smoke pipeline-cli-smoke workflow-security'
REQUIRED_NAMES='gitleaks verify audit-rbac vulncheck lint test build test-integration helm docker-build preflight kind-smoke pipeline-cli-smoke workflow-security tool-pins-selftest'

cat >"${TMP}/gh" <<'MOCK'
#!/usr/bin/env bash
Expand Down
2 changes: 1 addition & 1 deletion hack/release/verify-eligibility.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ fi

required_checks=(
gitleaks verify audit-rbac vulncheck lint test build test-integration helm
docker-build preflight kind-smoke pipeline-cli-smoke workflow-security
docker-build preflight kind-smoke pipeline-cli-smoke workflow-security tool-pins-selftest
)

main_sha="$(gh api "repos/${REPO}/commits/${DEFAULT_BRANCH}" --jq .sha)"
Expand Down
Loading
Loading