Migrate canonical .golangci.yml from gomodguard to gomodguard_v2 #47

Open
clawbot wants to merge 1 commits from fix/25-gomodguard-v2 into main
Collaborator

Speculative — pending your ruling. Answers #25 and unblocks the downstream sneak/dnswatcher#123.

Policy disclosure up front: REPO_POLICIES.md line 261 says .golangci.yml "must NEVER be modified by an agent, only manually by the user". A previous agent read that as binding here too and declined even a branch. This PR was requested explicitly, so it exists as a branch you can merge, amend, or delete. Nothing is merged.

Problem

golangci-lint v2.12.0 deprecated gomodguard. The canonical config uses default: all, so the deprecated linter is auto-enabled and every lint run in every consuming Go repo emits:

level=warning msg="The linter 'gomodguard' is deprecated (since v2.12.0) due to: new major version. Replaced by gomodguard_v2."
level=warning msg="Suggested new configuration:\nlinters:\n  enable:\n    - gomodguard_v2\n"

The change

One line, in the existing linters.disable block, matching the wsl entry directly above it:

    - gomodguard       # Deprecated, replaced by gomodguard_v2

No settings restructuring was required, because there were no settings to restructure. The canonical config configures no gomodguard settings at all — the linter was enabled solely by default: all. So there is no module policy to carry across; the migration is exclusively about which linter runs.

That is worth stating plainly: the org has never expressed a module allow/block policy through this linter, and this PR does not invent one.

Research findings (v2.12.2 sources, not assumptions)

The settings schema did change materially — it is not a rename. Had settings existed, they would have needed restructuring. From .golangci.next.reference.yml at v2.12.2 and pkg/golinters/gomodguard/:

gomodguard gomodguard_v2
allowed.modules: [string] allowed: [{module: ...}]
allowed.domains: [string] no equivalent; upstream's own migration maps domain D to {module: "D/.*", match-type: regex}
blocked.modules: [{name: {recommendations, reason}}] (list of single-key maps) blocked: [{module, recommendations, reason}] (flat)
blocked.versions: [{name: {version, reason}}] folded into blocked via a version field
blocked.local-replace-directives local-replace-directives, moved to the top level of the settings block
no match modes new match-type: exact (default) / prefix / regex

Two further findings from reading the source rather than the docs:

  • Upstream's own Migration function in gomodguard.go silently drops blocked.versions — it iterates old.Blocked.Modules only. Anyone migrating a config that used version constraints should not trust the auto-suggested output. Not applicable here, but worth recording.
  • Both linters can be listed simultaneously. They are two independent linter.NewConfig registrations in pkg/lint/lintersdb/builder_linter.go; there is no mutual exclusion. gomodguard_v2 is therefore already enabled by default: all, which is why this PR only disables the old one and adds no enable: block. The warning is attached to gomodguard itself, so silencing it requires removing the old one, not adding the new one.

Deprecation sweep (issue #25 asked for zero warnings, not just this one): grep for DeprecatedWarning across the v2.12.2 registry returns exactly two linters — gomodguard and wsl. wsl is already disabled. After this change the canonical config has zero deprecated linters enabled. The sweep that issue #25 noted had never been done is now done, and it is complete.

Evidence: the config is genuinely in effect

Verified against a throwaway sneak/dnswatcher clone at a scratch path (never committed to, never pushed, deleted afterwards) using that repo's own make lint. Local golangci-lint is v2.12.2, the pinned version.

1. Baseline, canonical config unmodified — reproduces the warning:

level=warning msg="The linter 'gomodguard' is deprecated (since v2.12.0) due to: new major version. Replaced by gomodguard_v2."
level=warning msg="Suggested new configuration:\nlinters:\n  enable:\n    - gomodguard_v2\n"
0 issues.

2. Negative control — with the candidate config plus a temporary gomodguard_v2 settings block blocking github.com/miekg/dns (a direct dependency), make lint FAILS and names the finding:

internal/resolver/dns_client.go:7:2: import of package `github.com/miekg/dns` is blocked because the module is in the blocked modules list. NEGATIVE CONTROL: proving gomodguard_v2 settings are applied. (gomodguard_v2)
	"github.com/miekg/dns"
	^
internal/resolver/iterative.go:12:2: import of package `github.com/miekg/dns` is blocked because the module is in the blocked modules list. NEGATIVE CONTROL: proving gomodguard_v2 settings are applied. (gomodguard_v2)
internal/resolver/resolver_test.go:13:2: import of package `github.com/miekg/dns` is blocked because the module is in the blocked modules list. NEGATIVE CONTROL: proving gomodguard_v2 settings are applied. (gomodguard_v2)
3 issues:
* gomodguard_v2: 3
make: *** [Makefile:26: lint] Error 1

Findings attributed to gomodguard_v2 with a non-zero exit prove three things at once: the linter is enabled by default: all, the linters.settings.gomodguard_v2 key is recognised rather than silently ignored, and the flat v2 schema above is the correct one.

3. The silent-ignore trap, demonstrated. Same rule, same file, one character changed — key mistyped as gomodguard-v2 instead of gomodguard_v2:

0 issues.
=== exit: 0 ===

A config that applies nothing is indistinguishable from a clean run. This is the exact failure mode that hid the v1 linters-settings block, and it is why the green in step 4 is reported only alongside step 2.

4. Reverted to the exact committed candidate — warning gone, clean:

0 issues.
=== exit: 0 ===

Hashes

  • Old: 021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb
  • New: 9dc33938311bd7319b21585cba45052f6edb1ab4305dafcd3816abd7ffd18a5b

Every repo vendoring this file must re-vendor it after merge. Consuming repos verify by sha256 and will mismatch the moment this lands: dnswatcher, webhooker, vaultik, rgoue, cattbox, sfdupes, attrsum, upaas, simplelog, mfer, secret, bsfirehose, pixa. Open issues asserting the old hash also need re-baselining — sneak/dnswatcher#123 among them. A propagation issue is warranted on merge; I have not filed one, since it is moot if you close this.

For the record, REPO_POLICIES.md does not currently publish the config's sha256 anywhere, so nothing in it needs updating for this change. Issue #25's definition of done assumed it did.

Validation

make fmt produced no changes (it formats markdown only; this is YAML). make check passes.

Speculative — pending your ruling. Answers https://git.eeqj.de/sneak/prompts/issues/25 and unblocks the downstream https://git.eeqj.de/sneak/dnswatcher/issues/123. **Policy disclosure up front:** `REPO_POLICIES.md` line 261 says `.golangci.yml` "must _NEVER_ be modified by an agent, only manually by the user". A previous agent read that as binding here too and declined even a branch. This PR was requested explicitly, so it exists as a branch you can merge, amend, or delete. Nothing is merged. ## Problem golangci-lint v2.12.0 deprecated `gomodguard`. The canonical config uses `default: all`, so the deprecated linter is auto-enabled and every lint run in every consuming Go repo emits: ``` level=warning msg="The linter 'gomodguard' is deprecated (since v2.12.0) due to: new major version. Replaced by gomodguard_v2." level=warning msg="Suggested new configuration:\nlinters:\n enable:\n - gomodguard_v2\n" ``` ## The change One line, in the existing `linters.disable` block, matching the `wsl` entry directly above it: ```yaml - gomodguard # Deprecated, replaced by gomodguard_v2 ``` **No settings restructuring was required, because there were no settings to restructure.** The canonical config configures no `gomodguard` settings at all — the linter was enabled solely by `default: all`. So there is no module policy to carry across; the migration is exclusively about which linter runs. That is worth stating plainly: the org has never expressed a module allow/block policy through this linter, and this PR does not invent one. ## Research findings (v2.12.2 sources, not assumptions) **The settings schema did change materially — it is not a rename.** Had settings existed, they would have needed restructuring. From [`.golangci.next.reference.yml` at v2.12.2](https://github.com/golangci/golangci-lint/blob/v2.12.2/.golangci.next.reference.yml) and [`pkg/golinters/gomodguard/`](https://github.com/golangci/golangci-lint/tree/v2.12.2/pkg/golinters/gomodguard): | `gomodguard` | `gomodguard_v2` | | --- | --- | | `allowed.modules: [string]` | `allowed: [{module: ...}]` | | `allowed.domains: [string]` | no equivalent; upstream's own migration maps domain `D` to `{module: "D/.*", match-type: regex}` | | `blocked.modules: [{name: {recommendations, reason}}]` (list of single-key maps) | `blocked: [{module, recommendations, reason}]` (flat) | | `blocked.versions: [{name: {version, reason}}]` | folded into `blocked` via a `version` field | | `blocked.local-replace-directives` | `local-replace-directives`, moved to the **top level** of the settings block | | no match modes | new `match-type: exact` (default) / `prefix` / `regex` | Two further findings from reading the source rather than the docs: - Upstream's own `Migration` function in `gomodguard.go` **silently drops `blocked.versions`** — it iterates `old.Blocked.Modules` only. Anyone migrating a config that used version constraints should not trust the auto-suggested output. Not applicable here, but worth recording. - **Both linters can be listed simultaneously.** They are two independent `linter.NewConfig` registrations in `pkg/lint/lintersdb/builder_linter.go`; there is no mutual exclusion. `gomodguard_v2` is therefore *already* enabled by `default: all`, which is why this PR only disables the old one and adds no `enable:` block. The warning is attached to `gomodguard` itself, so silencing it requires removing the old one, not adding the new one. **Deprecation sweep** (issue #25 asked for zero warnings, not just this one): `grep` for `DeprecatedWarning` across the v2.12.2 registry returns exactly two linters — `gomodguard` and `wsl`. `wsl` is already disabled. After this change the canonical config has **zero** deprecated linters enabled. The sweep that issue #25 noted had never been done is now done, and it is complete. ## Evidence: the config is genuinely in effect Verified against a throwaway `sneak/dnswatcher` clone at a scratch path (never committed to, never pushed, deleted afterwards) using that repo's own `make lint`. Local golangci-lint is v2.12.2, the pinned version. **1. Baseline, canonical config unmodified** — reproduces the warning: ``` level=warning msg="The linter 'gomodguard' is deprecated (since v2.12.0) due to: new major version. Replaced by gomodguard_v2." level=warning msg="Suggested new configuration:\nlinters:\n enable:\n - gomodguard_v2\n" 0 issues. ``` **2. Negative control** — with the candidate config plus a temporary `gomodguard_v2` settings block blocking `github.com/miekg/dns` (a direct dependency), `make lint` FAILS and names the finding: ``` internal/resolver/dns_client.go:7:2: import of package `github.com/miekg/dns` is blocked because the module is in the blocked modules list. NEGATIVE CONTROL: proving gomodguard_v2 settings are applied. (gomodguard_v2) "github.com/miekg/dns" ^ internal/resolver/iterative.go:12:2: import of package `github.com/miekg/dns` is blocked because the module is in the blocked modules list. NEGATIVE CONTROL: proving gomodguard_v2 settings are applied. (gomodguard_v2) internal/resolver/resolver_test.go:13:2: import of package `github.com/miekg/dns` is blocked because the module is in the blocked modules list. NEGATIVE CONTROL: proving gomodguard_v2 settings are applied. (gomodguard_v2) 3 issues: * gomodguard_v2: 3 make: *** [Makefile:26: lint] Error 1 ``` Findings attributed to `gomodguard_v2` with a non-zero exit prove three things at once: the linter is enabled by `default: all`, the `linters.settings.gomodguard_v2` key is recognised rather than silently ignored, and the flat v2 schema above is the correct one. **3. The silent-ignore trap, demonstrated.** Same rule, same file, one character changed — key mistyped as `gomodguard-v2` instead of `gomodguard_v2`: ``` 0 issues. === exit: 0 === ``` A config that applies nothing is indistinguishable from a clean run. This is the exact failure mode that hid the v1 `linters-settings` block, and it is why the green in step 4 is reported only alongside step 2. **4. Reverted to the exact committed candidate** — warning gone, clean: ``` 0 issues. === exit: 0 === ``` ## Hashes - Old: `021cc83f4e6fc7c31b95b34b846723dfcf20b66b7baeea1dc40406e643346bcb` - New: `9dc33938311bd7319b21585cba45052f6edb1ab4305dafcd3816abd7ffd18a5b` **Every repo vendoring this file must re-vendor it after merge.** Consuming repos verify by sha256 and will mismatch the moment this lands: dnswatcher, webhooker, vaultik, rgoue, cattbox, sfdupes, attrsum, upaas, simplelog, mfer, secret, bsfirehose, pixa. Open issues asserting the old hash also need re-baselining — https://git.eeqj.de/sneak/dnswatcher/issues/123 among them. A propagation issue is warranted on merge; I have not filed one, since it is moot if you close this. For the record, `REPO_POLICIES.md` does **not** currently publish the config's sha256 anywhere, so nothing in it needs updating for this change. Issue #25's definition of done assumed it did. ## Validation `make fmt` produced no changes (it formats markdown only; this is YAML). `make check` passes.
clawbot added 1 commit 2026-08-10 16:17:19 +02:00
config: migrate gomodguard to gomodguard_v2 in canonical .golangci.yml
All checks were successful
check / check (push) Successful in 6s
2d1f123e65
golangci-lint v2.12.0 deprecated `gomodguard` in favour of `gomodguard_v2`.
Because the canonical config uses `default: all`, the deprecated linter is
auto-enabled and every lint run in every consuming Go repo emits a
deprecation warning.

`gomodguard` and `gomodguard_v2` are two independently registered linters
in v2.12.2, so `gomodguard_v2` is already enabled by `default: all`; only
the deprecated one needs disabling. This mirrors the existing `wsl` entry,
which was disabled for the same reason when `wsl_v5` superseded it.

No settings migration was required: the canonical config configures no
gomodguard settings at all.

Refs: #25
Author
Collaborator

[manager] One ruling needed, independent of whether you take this PR.

REPO_POLICIES.md line 261 forbids agents from modifying .golangci.yml, unqualified. Read literally that also forbids editing the canonical copy here — which is the only place it can ever be fixed. An earlier agent read it that way and declined to open even a speculative branch; this one proceeded because the PR was explicitly requested. The rule as written contradicts the practice, so it recurs on every agent that meets it.

Options:

  1. Scope the rule to consuming repos — "agents must not modify a vendored .golangci.yml; changes go to sneak/prompts". Keeps the protection that matters (no local divergence) and unblocks the only place fixes can land.
  2. Leave it unqualified and enforce it here too — canonical config changes become owner-only, and agents may file issues but not PRs against it.

Recommendation: option 1. The rule's purpose is preventing silent per-repo divergence from canonical, not freezing canonical itself. Option 2 also means the deprecation warning in this PR could only ever be fixed by you by hand.

Assigning to you for the ruling. Not blocking anything — dnswatcher work continues either way.

**[manager] One ruling needed, independent of whether you take this PR.** `REPO_POLICIES.md` line 261 forbids agents from modifying `.golangci.yml`, **unqualified**. Read literally that also forbids editing the canonical copy here — which is the only place it can ever be fixed. An earlier agent read it that way and declined to open even a speculative branch; this one proceeded because the PR was explicitly requested. The rule as written contradicts the practice, so it recurs on every agent that meets it. Options: 1. **Scope the rule to consuming repos** — "agents must not modify a vendored `.golangci.yml`; changes go to `sneak/prompts`". Keeps the protection that matters (no local divergence) and unblocks the only place fixes can land. 2. **Leave it unqualified and enforce it here too** — canonical config changes become owner-only, and agents may file issues but not PRs against it. **Recommendation: option 1.** The rule's purpose is preventing silent per-repo divergence from canonical, not freezing canonical itself. Option 2 also means the deprecation warning in this PR could only ever be fixed by you by hand. Assigning to you for the ruling. Not blocking anything — dnswatcher work continues either way.
sneak was assigned by clawbot 2026-08-10 16:18:22 +02:00
All checks were successful
check / check (push) Successful in 6s
Required
Details
This pull request is blocked because it's outdated.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/25-gomodguard-v2:fix/25-gomodguard-v2
git checkout fix/25-gomodguard-v2
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/prompts#47