Skip to content

Commit 322f695

Browse files
authored
build: Go floor and toolchain at 1.26.6, the version every builder of the module supports (#12)
Signed-off-by: Vuong Nguyen <vuong@contexing.com>
1 parent 3fe783f commit 322f695

17 files changed

Lines changed: 174 additions & 41 deletions

File tree

.github/workflows/ci.yml

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,18 @@ jobs:
279279
submodules: recursive
280280
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
281281
with:
282-
go-version: "1.27.0"
282+
go-version: "1.26.6"
283283
cache-dependency-path: packages/sdk-go/go.sum
284+
# The toolchain the module declares is the toolchain this job runs. A
285+
# setup-go that resolved to anything else is a failure, not a footnote.
286+
- name: The Go toolchain is the pinned one (scripts/release-pins.env)
287+
run: |
288+
set -euo pipefail
289+
. scripts/lib/release-pins.sh
290+
load_release_pins scripts/release-pins.env
291+
INSTALLED="$(go env GOVERSION)"
292+
test "$INSTALLED" = "go${GO_VERSION}" || { echo "::error::Go $INSTALLED is not the pinned go${GO_VERSION}"; exit 1; }
293+
echo "Go $INSTALLED"
284294
- name: gofmt
285295
working-directory: packages/sdk-go
286296
run: test -z "$(gofmt -l .)" || (gofmt -l . && exit 1)
@@ -311,11 +321,20 @@ jobs:
311321
node-version: 24
312322
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
313323
with:
314-
go-version: "1.27.0"
324+
go-version: "1.26.6"
315325
cache-dependency-path: packages/sdk-go/go.sum
316326
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
317327
with:
318328
python-version: "3.12"
329+
# Parity compares three CLIs; the Go one must be built by the pinned toolchain.
330+
- name: The Go toolchain is the pinned one (scripts/release-pins.env)
331+
run: |
332+
set -euo pipefail
333+
. scripts/lib/release-pins.sh
334+
load_release_pins scripts/release-pins.env
335+
INSTALLED="$(go env GOVERSION)"
336+
test "$INSTALLED" = "go${GO_VERSION}" || { echo "::error::Go $INSTALLED is not the pinned go${GO_VERSION}"; exit 1; }
337+
echo "Go $INSTALLED"
319338
- run: npm ci
320339
# Node, Go, Python CLIs on identical inputs; asserts byte-identical output.
321340
- run: npm run parity
@@ -332,11 +351,21 @@ jobs:
332351
node-version: 24
333352
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
334353
with:
335-
go-version: "1.27.0"
354+
go-version: "1.26.6"
336355
cache-dependency-path: packages/sdk-go/go.sum
337356
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
338357
with:
339358
python-version: "3.12"
359+
# The binary compiled below is the one the offline leg runs, so the
360+
# toolchain that compiles it is checked while the network still exists.
361+
- name: The Go toolchain is the pinned one (scripts/release-pins.env)
362+
run: |
363+
set -euo pipefail
364+
. scripts/lib/release-pins.sh
365+
load_release_pins scripts/release-pins.env
366+
INSTALLED="$(go env GOVERSION)"
367+
test "$INSTALLED" = "go${GO_VERSION}" || { echo "::error::Go $INSTALLED is not the pinned go${GO_VERSION}"; exit 1; }
368+
echo "Go $INSTALLED"
340369
- run: npm ci
341370
- run: npm run build -w packages/sdk
342371
- name: Install the Python SDK

.github/workflows/dependency-audit.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,18 @@ jobs:
8585
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
8686
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
8787
with:
88-
go-version: "1.27.0"
88+
go-version: "1.26.6"
8989
cache-dependency-path: packages/sdk-go/go.sum
90+
# The standard library govulncheck reports on is the toolchain's own, so a
91+
# scan on an unpinned Go audits a standard library the module never ships.
92+
- name: The Go toolchain is the pinned one (scripts/release-pins.env)
93+
run: |
94+
set -euo pipefail
95+
. scripts/lib/release-pins.sh
96+
load_release_pins scripts/release-pins.env
97+
INSTALLED="$(go env GOVERSION)"
98+
test "$INSTALLED" = "go${GO_VERSION}" || { echo "::error::Go $INSTALLED is not the pinned go${GO_VERSION}"; exit 1; }
99+
echo "Go $INSTALLED"
90100
# govulncheck reports the vulnerabilities this code can actually reach, so
91101
# a finding here is a call path, not an inventory entry.
92102
- name: govulncheck

.github/workflows/release-checks.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
python-version: "3.12"
5656
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
5757
with:
58-
go-version: "1.27.0"
58+
go-version: "1.26.6"
5959
cache-dependency-path: packages/sdk-go/go.sum
6060
# The npm the publish jobs run through, installed here for the same reason
6161
# the Python and Go versions are pinned here: a rehearsal on a different
@@ -69,6 +69,16 @@ jobs:
6969
INSTALLED="$(npm --version)"
7070
test "$INSTALLED" = "$NPM_VERSION" || { echo "::error::npm $INSTALLED is not the pinned $NPM_VERSION"; exit 1; }
7171
echo "npm $INSTALLED"
72+
# And the Go the runner actually resolved is asserted here, rather than
73+
# only appearing in the smoke's prerequisites line.
74+
- name: The Go toolchain is the pinned one (scripts/release-pins.env)
75+
run: |
76+
set -euo pipefail
77+
. scripts/lib/release-pins.sh
78+
load_release_pins scripts/release-pins.env
79+
INSTALLED="$(go env GOVERSION)"
80+
test "$INSTALLED" = "go${GO_VERSION}" || { echo "::error::Go $INSTALLED is not the pinned go${GO_VERSION}"; exit 1; }
81+
echo "Go $INSTALLED"
7282
- run: npm ci
7383
- name: Build artifacts, cold-install each, assert CLI battery + cross-SDK parity
7484
run: npm run smoke:prepublish
@@ -145,8 +155,18 @@ jobs:
145155
fetch-depth: 0 # full history so goreleaser sees tags
146156
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
147157
with:
148-
go-version: "1.27.0"
158+
go-version: "1.26.6"
149159
cache-dependency-path: packages/sdk-go/go.sum
160+
# The six published binaries are compiled here; the toolchain that compiles
161+
# them is the one the module declares.
162+
- name: The Go toolchain is the pinned one (scripts/release-pins.env)
163+
run: |
164+
set -euo pipefail
165+
. scripts/lib/release-pins.sh
166+
load_release_pins scripts/release-pins.env
167+
INSTALLED="$(go env GOVERSION)"
168+
test "$INSTALLED" = "go${GO_VERSION}" || { echo "::error::Go $INSTALLED is not the pinned go${GO_VERSION}"; exit 1; }
169+
echo "Go $INSTALLED"
150170
# goreleaser cannot parse a path-prefixed tag as a version, so the run gets
151171
# a LOCAL bare tag (never pushed; no repo-wide vX.Y.Z ref exists).
152172
- name: Prepare the goreleaser version tag (local only)

.github/workflows/release.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,18 @@ jobs:
222222
fetch-depth: 0 # full history so goreleaser sees tags
223223
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
224224
with:
225-
go-version: "1.27.0"
225+
go-version: "1.26.6"
226226
cache-dependency-path: packages/sdk-go/go.sum
227+
# The binaries this job publishes are compiled here, so the toolchain that
228+
# compiles them is asserted before anything irreversible happens.
229+
- name: The Go toolchain is the pinned one (scripts/release-pins.env)
230+
run: |
231+
set -euo pipefail
232+
. scripts/lib/release-pins.sh
233+
load_release_pins scripts/release-pins.env
234+
INSTALLED="$(go env GOVERSION)"
235+
test "$INSTALLED" = "go${GO_VERSION}" || { echo "::error::Go $INSTALLED is not the pinned go${GO_VERSION}"; exit 1; }
236+
echo "Go $INSTALLED"
227237
# goreleaser can't parse the path-prefixed tag as a version, so create a
228238
# LOCAL bare tag for the run (never pushed; no repo-wide vX.Y.Z ref).
229239
- name: Prepare goreleaser version tag (local only)

CHANGELOG.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ the frozen v1.0 line and all reference packages move to 1.4.1 together.
2323
Go SDK. The scanners are pinned like everything else on the release path.
2424
- **A pin check for the release path**, `scripts/check-release-pins.sh`: it refuses `@latest`,
2525
`--upgrade`, a `pip install` or `npm install -g` without an exact version, a `go-version`
26-
without a patch component, and a `uses:` without a 40-hex SHA. Contributors get it as a stage
27-
of the pre-push hook; CI runs it as its own job, with a self-test that proves each rule still
28-
fires, and the pre-publish smoke runs it in layer 0. Alongside it, the PyPI upload's own
29-
`twine check --strict` now runs on the gates that come before a tag: in the pre-publish
30-
smoke, in CI on every pull request, and in the pre-push hook when a `release/*` or `rc/*`
31-
ref is pushed. The tag-triggered release workflow runs the same check once more on the
32-
distribution it built, so the upload action is never the first thing to see a rejection.
26+
that lacks a patch component or disagrees with the module's own `go` directive, and a
27+
`uses:` without a 40-hex SHA. Contributors get it as a stage of the pre-push hook; CI runs
28+
it as its own job, with a self-test that proves each rule still fires, and the pre-publish
29+
smoke runs it in layer 0. Alongside it, the PyPI upload's own `twine check --strict` now
30+
runs on the gates that come before a tag: in the pre-publish smoke, in CI on every pull
31+
request, and in the pre-push hook when a `release/*` or `rc/*` ref is pushed. The
32+
tag-triggered release workflow runs the same check once more on the distribution it built,
33+
so the upload action is never the first thing to see a rejection.
3334
- **A browser smoke suite** at `packages/e2e` (Playwright on Chromium) over the served viewer,
3435
an exported tree, and the site, so the live and static renderings are checked against one
3536
set of assertions: `npm run e2e` locally, a `ui-smoke` job in CI that keeps traces and
@@ -82,13 +83,15 @@ the frozen v1.0 line and all reference packages move to 1.4.1 together.
8283
MCP server on Node 22 and 24, the floor its `engines.node` declares. The pre-publish smoke
8384
installs the SDK, `create-leji`, and the MCP server tarballs into a Node 22 container and
8485
drives all three there, and its result line says so when Docker is absent and the leg is
85-
skipped. The Go SDK is built and tested at Go 1.27.0, which is now also the floor its
86+
skipped. The Go SDK is built and tested at Go 1.26.6, which is now also the floor its
8687
`go.mod` declares, so every floor named here is a floor CI exercises.
87-
- **The Go floor moves to 1.27.0.** `packages/sdk-go/go.mod` declares `go 1.27.0` (up from
88+
- **The Go floor moves to 1.26.6.** `packages/sdk-go/go.mod` declares `go 1.26.6` (up from
8889
`go 1.23`), and `golang.org/x/text` moves to v0.41.0 with it. Building the Go SDK, or
89-
`go install`ing the `leji` binary from source, now needs Go 1.27.0 or newer; the published
90-
release binaries are unaffected, since they carry no toolchain requirement. `CONTRIBUTING.md`
91-
and the Go setup script state the new floor.
90+
`go install`ing the `leji` binary from source, now needs Go 1.26.6 or newer: it is a strict
91+
minimum, so an older 1.26 patch downloads the matching toolchain automatically under Go's
92+
default `GOTOOLCHAIN=auto` and fails under `GOTOOLCHAIN=local`. The published release
93+
binaries are unaffected, since they carry no toolchain requirement. `CONTRIBUTING.md` and the
94+
Go setup script state the new floor.
9295
- **The test suites pin what they used to sample**: the badge and canary suites in all three
9396
SDKs share one directory-snapshot helper held to a golden fixture, the capture-cap test
9497
asserts bytes and termination rather than elapsed time, and the homepage's hero transcript is

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ npm run setup:go # Go: installs goreleaser (the only Go dev tool not bundled wit
1111
npm test # runs the Node, Python, and Go suites
1212
```
1313

14-
Prerequisites: Node 24+, a Python >=3.10 (the Python SDK pins 3.12 via `packages/sdk-py/.python-version`), and Go 1.27+. The `setup:*` scripts detect each toolchain and print install hints if it is missing. Both are idempotent and machine-local (the Python `.venv` is git-ignored), so re-run them after cloning or switching machines.
14+
Prerequisites: Node 24+, a Python >=3.10 (the Python SDK pins 3.12 via `packages/sdk-py/.python-version`), and Go 1.26.6+. The `setup:*` scripts detect each toolchain and print install hints if it is missing. Both are idempotent and machine-local (the Python `.venv` is git-ignored), so re-run them after cloning or switching machines.
1515

1616
**Running your work-in-progress CLI**, two channels, one machine-wide at a time:
1717

@@ -25,7 +25,7 @@ Prerequisites: Node 24+, a Python >=3.10 (the Python SDK pins 3.12 via `packages
2525
- **Spec proposals.** Open an issue first: the problem, the intent, and the lived case behind it. Leji specifies proven practice; proposals grounded in something a real team does carry more weight than ideas in the abstract.
2626
- **Pull requests.** Normative changes (anything under `spec/` or `schemas/`) ride PR review and require a `CHANGELOG.md` entry plus a machine-readable `CHANGELOG.json` entry. Yes, the spec dogfoods itself.
2727
- **Contributor terms.** Every commit needs a DCO sign-off (`git commit -s`); contributions ship under the license for their content type. See [Contributor terms](#contributor-terms).
28-
- **Tooling.** SDK changes need tests and must keep `leji validate` passing against `examples/`. The Node, Python, and Go SDKs (`packages/sdk`, `packages/sdk-py`, `packages/sdk-go`) are behaviorally identical: a behavior change in one rides into all three, pinned by the shared `fixtures/` suite. Behavior develops and proves out fully in the TypeScript SDK first, the canonical implementation, against the LIVE channel ([testing-cli-adoptions](docs/practice/testing-cli-adoptions.md)); the Go and Python ports are made only from settled TypeScript behavior, pinned by the shared fixtures at port time. The Go SDK builds with Go 1.27+; `gofmt`, `go vet ./...`, and `go test ./...` must pass.
28+
- **Tooling.** SDK changes need tests and must keep `leji validate` passing against `examples/`. The Node, Python, and Go SDKs (`packages/sdk`, `packages/sdk-py`, `packages/sdk-go`) are behaviorally identical: a behavior change in one rides into all three, pinned by the shared `fixtures/` suite. Behavior develops and proves out fully in the TypeScript SDK first, the canonical implementation, against the LIVE channel ([testing-cli-adoptions](docs/practice/testing-cli-adoptions.md)); the Go and Python ports are made only from settled TypeScript behavior, pinned by the shared fixtures at port time. The Go SDK builds with Go 1.26.6+; `gofmt`, `go vet ./...`, and `go test ./...` must pass.
2929
- **Language policy (Node side).** TypeScript + ESM everywhere: SDK source and tests, the site (`astro.config.ts` included), and repo scripts (run natively by Node's type stripping; develop on Node 24+). The one deliberate exception is `packages/create-leji/index.js`, a zero-build published shim. No `.mjs`: every package declares `"type": "module"`.
3030
- **Style.** Spec prose is plain English, normative keywords per RFC 2119 (MUST/SHOULD/MAY), human-readable first.
3131

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module example.com/bad
2+
3+
// Rule 10, the other half: the module declares a floor the tuple does not name,
4+
// so the workflows and go.mod have drifted apart.
5+
go 1.26.5
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Go patch drift
2+
# Rule 10: a patch-level go-version that is not the GO_VERSION the tuple names.
3+
# It satisfies rule 5, so the job looks pinned while running a toolchain the
4+
# module never declared.
5+
6+
on: [push]
7+
8+
jobs:
9+
go:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
13+
with:
14+
go-version: "1.26.5"

fixtures/release-path/pins/good/.github/workflows/pinned.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
1717
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
1818
with:
19-
go-version: "1.27.0"
19+
go-version: "1.26.6"
2020
- run: npm install -g npm@12.0.2 && npm --version
2121
# The same install written the other accepted way: the version comes from
2222
# the tuple itself, so there is no literal here that could drift from it.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module example.com/good
2+
3+
// The `go` directive the tuple names, so rule 10 has a passing shape to read.
4+
go 1.26.6

0 commit comments

Comments
 (0)