Skip to content

fix(ci): preflight-tidy gate 1 allow indirect-only go.mod (#973)#974

Merged
millerjp merged 1 commit into
mainfrom
fix/973-preflight-tidy-indirect
Jun 12, 2026
Merged

fix(ci): preflight-tidy gate 1 allow indirect-only go.mod (#973)#974
millerjp merged 1 commit into
mainfrom
fix/973-preflight-tidy-indirect

Conversation

@millerjp

Copy link
Copy Markdown
Contributor

Summary

v0.2.4 dispatch failed at gate 1 with go.mod modified — aborting because tidy adjusted // indirect requires across 11 sub-module go.mod files (transitive deps shifting after v0.2.2: adding axonops/syncmap, golang.org/x/crypto; removing kr/text). These are routine Go-toolchain housekeeping, not engineering changes.

This refines gate 1 to classify go.mod changes:

  • indirect-only → pass through to gates 2+
  • direct require modified → reject with preflight-tidy: go.mod direct require modified — aborting
  • module / go / toolchain / replace / retract / exclude directive modified → reject with preflight-tidy: go.mod module/go/toolchain/replace directive modified — aborting

The single msgGoModModified constant is replaced by two more-specific constants. Unrecognised line shapes fail closed to the directive reject path.

Why this is a bug, not a workaround

Gate 1 was designed during #967 to block any go.mod change because a new direct require could introduce a malicious module. But that threat manifests through direct-require changes, not indirect-line shuffles — and gate 4 (sum.golang.org cross-check) catches any axonops/audit/* tampering that would cause those adjustments to be malicious. As written, gate 1 made the release flow unusable: every release naturally produces some transitive churn.

Test plan

  • go test ./cmd/release-tool/... -race -count=1 — all release-tool tests pass.
  • golangci-lint run ./cmd/release-tool/... — 0 issues.
  • make test-release-scripts — 117/117 pass (bats grep anchors updated).
  • make regen-llms-check — clean.
  • 7 new Go tests added: ..._DirectRequireAdd / Bump / ModuleDirective / GoDirective / ToolchainDirective / ReplaceDirective / IndirectOnlyAllowed. The IndirectOnlyAllowed fixture mirrors v0.2.4's actual blocked diff shape.
  • CI green on this PR.
  • v0.2.4 dispatch reaches tag-all after this merges.

Closes #973.

v0.2.4's first dispatch (run 27370455797) failed at gate 1 with
`preflight-tidy: go.mod modified — aborting`. The actual `make tidy`
diff modified 11 sub-module go.mod files — but every change was a
`// indirect` line adjustment: tidy was adding axonops/syncmap +
golang.org/x/crypto and removing kr/text as the transitive set
shifted after v0.2.2's publication.

These are routine Go-toolchain housekeeping, not engineering
changes. Real engineering changes (a new direct require, a version
bump on an existing direct require, a change to the module / go /
toolchain / replace / retract / exclude directives) ARE security
signals — but `// indirect`-only adjustments are not, and gate 4's
sum.golang.org cross-check catches any tampered axonops/audit/* hash
that would feed into them.

This refines gate 1 to classify go.mod changes into three buckets:
- goModIndirectOnly: pass through to gates 2+
- goModDirectRequire: reject with the new
  `preflight-tidy: go.mod direct require modified — aborting`
- goModDirective:     reject with the new
  `preflight-tidy: go.mod module/go/toolchain/replace directive
   modified — aborting`

The classifier is a line-oriented matcher (not a full go.mod
parser) — every line of a `go mod tidy` diff is either a known
directive prefix, a require entry with or without `// indirect`,
or a structural marker (`require (`, `)`, blank, in-block comment).
Unrecognised shapes fail closed to goModDirective.

Changes:
- cmd/release-tool/cmd_preflight_tidy_check.go: replace
  hasGoModChange with goModChangeKind enum + classifyGoModChanges
  + classifyGoModLine + startsWithGoModDirective helpers. New
  msgGoModDirectRequire + msgGoModDirective constants; old
  msgGoModModified retired.
- cmd/release-tool/cmd_preflight_tidy_check_test.go: replace the
  single TestPreflightTidyCheck_Gate1_GoModModifiedAborts test
  with 7 table-driven tests (DirectRequireAdd, DirectRequireBump,
  ModuleDirective, GoDirective, ToolchainDirective,
  ReplaceDirective, IndirectOnlyAllowed). The IndirectOnlyAllowed
  fixture mirrors v0.2.4's actual blocked diff shape.
- tests/release-scripts/release-yml-grep.bats: rename
  test_release_yml_preflight_tidy_enforces_go_sum_only →
  ..._rejects_direct_require_changes; update the property-style
  exact-strings test to anchor on both new strings.
- docs/releasing.md: failure-mode table row 1 reflects the
  new classification + both new error strings.
- CHANGELOG.md: [Unreleased] / Fixed entry.

Verifications:
- `go test ./cmd/release-tool/... -race -count=1` clean.
- `golangci-lint run ./cmd/release-tool/...` 0 issues.
- `make test-release-scripts` 117/117 pass.
- `make regen-llms-check` clean.

Closes #973.
@millerjp millerjp merged commit a28f731 into main Jun 12, 2026
35 of 39 checks passed
@millerjp millerjp deleted the fix/973-preflight-tidy-indirect branch June 12, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: preflight-tidy gate 1 rejects indirect-only go.mod changes (blocks every release)

1 participant