Repository navigation
Releases: andygeiss/baseline
Release list
v4.8.2 — Go 1.27.3, a floor for every program that can use TLS
A security pin. Go 1.27.3 stops crypto/x509 trusting a handful of intermediate certificates, and below it govulncheck fails every program that can use TLS.
The pin moves to Go 1.27.3. It shipped on 2026-10-19 with one advisory, GO-2026-6809, on Go's urgent track: Certificate.Verify now drops every chain through a small set of intermediate certificates Go no longer trusts (go.dev/issue/82345). VERSIONS.md, the pin line in stack/go.md, and both GOTOOLCHAIN commands in operations/ci.md moved together. The 1.27.3 source differs from 1.27.2 only inside crypto/x509, with no API change, so the canonical snippets were not re-run under it.
Who fails below the floor. The 1.27.2 floor caught every web application. This one catches every program that can reach Certificate.Verify, and every TLS handshake can. Under 1.27.2, a program whose only network call is http.Get fails, even to an http:// address, and so does one that only calls smtp.SendMail, tls.Dial or ListenAndServeTLS, or Certificate.Verify with no TLS at all. A program that only serves plain HTTP passes, and so does an SMTP client that never calls SendMail or StartTLS. The reference fails, from cmd/server. Under 1.27.3 all of them pass. The Go row says it in the bytes the old row took: every program that can use TLS, as every net/http client can.
What review found. Four rounds of two adversarial passes read the pin and executed its claims under both toolchains, with throwaway modules on each side of the line. They found 7, 1, 1 and 0 distinct defects. The one that mattered was in the first draft of the Go row: it said 1.27.2 fails "every program with an HTTP or SMTP client", which was false both ways, since SMTP without TLS passes and a bare tls.Dial fails. Round two found the record still treating TLS as the trigger rather than the call. The rest were in the record:
- two commit messages that said less or more than their diffs;
- an "it" that named the old pin and then
govulncheck; - a reason for keeping four 1.27.1 lines that held for one of them;
- the libc question, which had dropped out of the date bullet;
- an Owed promise that this run would read round four's fixes, which Andy then chose it would not;
- and that choice, recorded at first without what it means for the gate.
This release does not meet the gate's first condition for operations/ci.md. Andy chose to review it on its Go pin alone, and the last two passes came back clean. But the pin changes two lines of operations/ci.md, which also carries round four's fixes from v4.8.1 (aafa39f), and no pass has read those. VERIFICATION.md keeps them owed to the next run.
Not in this release. The child-process pattern sat out this tag as it sat out v4.8.0 and v4.8.1, and it is back on main in the commit after, still owed a run with round five's ten findings open. VERSIONS.md keeps its 2026-09-08 date: htmx 2.0.11 and modernc.org/sqlite v1.61.0 have shipped, and with the exact modernc.org/libc the driver names, which go get -u ./... moves, they wait for one ruling.
Budgets: no floor, change path or reach moves. The new Go row is as long as the old one, byte for byte.
Reference: baseline-reference v4.8.2, pinning c79c209: ./verify.sh exits 0 over 79 gates and make ci is green, both under Go 1.27.3. Under 1.27.2, ./verify.sh stops at its fifth step, govulncheck.
v4.8.1 — staticcheck reads Go 1.27.2 again
Staticcheck caught up with the Go pin, so the gate v4.8.0 waived is back everywhere it was waived. And the review v4.8.0 shipped without, of its eighth round's fixes, ran here.
Staticcheck 2026.2.2 reads Go 1.27.2. v4.8.0 pinned Go 1.27.2, whose compiler writes export data V5. Staticcheck 2026.2.1 could not read it (dominikh/go-tools#1832), so operations/ci.md ruled that a project takes its staticcheck line out under a dated README waiver until a release reads it. The fix landed on master on 2026-10-09 — v4.8.0's page, published the next morning, still called the issue open — and shipped in 2026.2.2 (v0.8.2) on 2026-10-12: "Staticcheck now supports the updated export data format used by Go 1.27.2." Under 1.27.2 it runs clean on the reference, where 2026.2.1 still fails.
How to tell the fix has shipped. ci.md said to put the line back "once it runs", which nobody could test. A staticcheck that cannot read the pin still checks the packages that import nothing, and the fixed release exits 1 on any finding too. The bullet now says to judge by the export-data error, under the pin. Then fix what the returning line reports, one commit per commit type, and put the line back, deleting the waiver, in a commit of its own.
Round eight, reviewed. v4.8.0 shipped its eighth round's eleven fixes unread, and Owed kept them for the next run. This was it. Four rounds of two adversarial passes read all ten that remain in the rules and executed their claims against Go 1.27.2, with toy modules, file:// proxies and bare remotes. They found 5, 5, 7 and 6 distinct defects, three of the first five in a draft of this release's own record and nearly all the rest in the shell mechanics of operations/ci.md and operations/cli-release.md. The ones that mattered most:
- The release check could empty the module cache. Its cleanup line read the
dthe line before set. Run alone in a fresh shell, the way an agent's shell tool runs it, it emptied this machine's own cache; in an interactive zsh with oh-my-zsh,dalready names a directory. The check is now one subshell:mktempsucceeds or nothing runs, a trap on EXIT, INT, TERM and HUP cleans up, andddies with the parentheses. - A failed check read as a pass. The block exited with its cleanup's status, and pasted whole after a red
make ciit still asked the proxy for the untagged version, which can delay the real check by up to 30 minutes. The tag-and-push line now stands alone. The check runs only oncegit ls-remoteshows the tag landed, and it exits with its own status. - Past v1, the check failed for every correct release. The install path needs the module's
/vN, and on the bare path@lateststays on the v1 line. The channel line, the check, the version bump and the CLI checklist now say so. - A put-back that held nothing. After
go getraises thegoline,go.modalready names the version that asked, so-require=<dep>@<current>held that very version; and the update section said to commit before it reached thegoline. The holds now name the version from before the update, whichgit show HEAD:go.modnames until you commit, and the line is read before the commit. - A tag on the wrong commit. The push names
HEAD:main, so a tag made on a branch cannot go out on a commitmainlacks.
This release does not meet the gate's first condition. After the third round, Andy chose one more and then the tag. Round four's seven fixes, aafa39f, went through the reference but no review pass, and VERIFICATION.md keeps them owed to the next run. Round eight's entry leaves Owed: its fixes have now been read and executed.
Not in this release. The child-process pattern sat out of this tag as it sat out of v4.8.0, and it is back on main in the commit after, still owed a run with round five's ten findings open. The canonical Go snippets were not re-run through 2026.2.2; v4.8.0 checked them with a rebuilt v0.8.1. No Last verified: date moved: the run read the sections it changed, not the documents whole. VERSIONS.md keeps its 2026-09-08 date: htmx 2.0.11 and modernc.org/sqlite v1.60.1 still wait for a ruling, and so does the exact modernc.org/libc the driver names, which go get -u ./... moves.
Budgets: library and web-application floors and change paths are unchanged. cli-tool's grew by the install box's 7: change path 5,311 → 5,318, floor 11,575 → 11,582. Reach grew with operations/: cli-tool 41,864 → 42,441, library 29,172 → 29,742, web-application 103,449 → 103,676.
Reference: baseline-reference v4.8.1, pinning aafa39f: ./verify.sh exits 0 over 79 gates, staticcheck among them, and make ci is green, both under Go 1.27.2.
v4.8.0 — Go 1.27.2, and a patch that outruns staticcheck
A security pin, and the rule it needed: Go 1.27.2 writes export data no staticcheck release reads yet.
The pin moves to Go 1.27.2. It shipped on 2026-10-08 with fifteen security fixes, thirteen in the standard library and two in the go command. Under 1.27.1, govulncheck in the reference reports twelve of them as called: both html/template escaper fixes through render, Range parsing through ServeContent and the static file server, and multipart header parsing through the upload handler. So the old pin fails that gate in every web application. Under 1.27.2 it reports none. VERSIONS.md, the pin line in stack/go.md, and both GOTOOLCHAIN commands in operations/ci.md moved together.
A Go patch can outrun staticcheck. The go.dev/issue/81188 fix, backported to 1.27.2, makes the compiler write export data V5. Staticcheck 2026.2.1, still the newest release, reads only V4, so it checks no package that imports the standard library (dominikh/go-tools#1832, open). operations/ci.md now rules the case: the staticcheck line comes out under a dated README waiver that names the upstream issue, the pin stays, and the plain line is tried again on every dependency update. It costs every project its staticcheck run until upstream ships, and nothing else.
How that rule got here. The first answer ran the line under GOTOOLCHAIN=go1.27.1, and seven review rounds took it apart. Under 1.27.1, a type that declares a generic method before a non-generic one brings the bug back, and staticcheck panics on it or misfiles its findings. And the prefixed line refuses any go.mod that names a later patch, which go get, go mod tidy and go mod init all write under the pin. Andy chose the dated waiver over iterating on the prefix or holding the tag.
stack/makefile.md rule 5 is now Adjustments. Besides the three layouts, it names what operations/ci.md lets a project's Makefile hold: the previous staticcheck version while a release of it breaks the gate, the line out under a dated waiver, and a go fix fixer switched off by name. That closes two v4.7.0 collisions, where ci.md told a project to do something rule 5's "exactly these" forbade. The three checklists' Makefile box reads "differing only as its rule 5 allows".
What the run found that is older than this release. The canonical fmt loop exited 0 when go fix did not converge, though its comment has said since v4.6.0 that it fails; it now fails after three passes. Four code blocks carried # comments, which macOS's interactive zsh hands to the command as words. And ci.md said the pin refuses any dependency that asks for a newer Go. It takes one that asks for its own patch and raises the go line, and ci.md now names the command that shows which module asked.
Not in this release. The child-process pattern, patterns/go-child-process.md: five rounds did not bring it to two clean passes, and the security pin did not wait on it. It is back on main in the commit after this tag, still owed a run, with round five's ten findings open. VERSIONS.md keeps its 2026-09-08 date: htmx 2.0.11 and modernc.org/sqlite v1.60.1 have shipped since, and 2.0.11 backports <hx-partial> from 4.0, which wants a ruling rather than a bump.
Budgets: every budgeted path got shorter. Change paths: cli-tool 5,332 → 5,311, library 4,351 → 4,340, web-application 7,985 → 7,963. Floors: cli-tool 11,597 → 11,575, library 12,504 → 12,494, web-application 21,363 → 21,342. Reach grew, mostly in operations/ci.md and stack/makefile.md.
This release does not meet the gate's first condition. Eight rounds of two adversarial passes each found defects, from 19 in the first to 11 in the eighth, and Andy stopped review there. Round eight's eleven fixes went through the reference but no review pass, and VERIFICATION.md keeps them owed to the next run. The ones worth remembering:
- The release block in
operations/cli-release.md, pasted as written, tagged and pushed after a redmake ci. - The
go listthat shows which module asked for a newer Go needed-eto list anything in the very state it was offered for. - Under 1.27.1, a dependency's type with its generic method declared first misfiled staticcheck's findings into the project importing it: a false warning on one method, a real one missed on another.
Reference: baseline-reference v4.8.0, pinning e7e378c: ./verify.sh exits 0 over 79 gates with staticcheck waived, and make ci is green. No staticcheck release runs under the pin, so this run's staticcheck findings came from v0.8.1 rebuilt against golang.org/x/tools v0.51.0, the one build that reads 1.27.2's export data.
v4.7.0 — import a package for what it does
Two changes, both about paying fewer tokens for the same rules.
The run log left VERIFICATION.md. It grew by one narrative per release and repeated the release page every tag already had. VERIFICATION.md went from about 13,900 words to 2,800. Run narratives now live only on release pages like this one, and the old log is in git history. The tag gate's fifth condition, the README, and the tombstone for a retired pattern all point here. The gate was reworded so it can be met in order: the first four conditions before the tag, the release page after it.
stack/go.md: import a package for what it does, not for one helper. No reflect, tests included. No regexp where strings or strconv will do. Outside the approved table, one small function from a permissively licensed module is copied with its full license notice, not imported. Crypto, escaping, and parsers are never copied. The rule states prohibitions only. Earlier drafts named replacements for reflect and DeepEqual that were not equivalent, and a competent Go engineer needs none. The Context and Zero values bullets were cut against it.
Budgets: the change path went from 8,000 to 7,985, and every floor rose 10 or 11 tokens. The next pattern still needs a budget decision first.
Five rounds of two adversarial passes found 19 defects: 18 in the baseline and 1 in the reference's SPEC.md. The last two passes over the baseline were clean. The ones worth remembering:
- The first draft's "copy the lines" contradicted the
slices,mapsandcmp.Orrows, whichgo fixenforces. - One copyright line satisfies no permissive license.
- Deleting the log left the gate asking for a release page before the tag existed.
Reference: baseline-reference v4.7.0, pinning d12a127, 79 gates, make ci green.
v4.6.0 — the Go 1.27 defect backlog
Go 1.27's standard-library delta is almost entirely noise for this stack. The value was the defects it exposed in rules the corpus already shipped: 31 corrections across 17 documents, six of them tier 1. No new pattern document, no version bump.
cfg.BaseURL was defined nowhere. go-email.md's tier-1 rule builds every emailed link from it, and no document said to add, parse, validate or set it — three consumers, zero definitions, including a checklist box. With no field to build from, the shortest path back to a working reset link is the r.Host line that document prints as the thing never to write. go-config.md now owns BASE_URL and the sender address, the two boot checks go-email.md already credited to it, and the check is on the scheme and the host rather than on err: url.Parse returns no error for evil.example, /reset, app.db or javascript:alert(1).
Two json/v2 failure modes nothing warned about. Marshal hands back the bytes it got through and a non-nil error where v1 handed back nil, so a MarshalWrite aimed at a ResponseWriter commits a 200 and half a body — measured, a recovering http.Error spliced its text into the broken JSON. And an error wrapping errors.ErrUnsupported is read by v2 as "use the default representation": a MarshalJSONTo written to redact a secret returned {"user":"ada","token":"hunter2"} with a nil error, while the []byte form errored loudly. The docs recommend the streaming form, so the recommended form is the unsafe one.
srv.URL is a real public domain, and this repository's own v4.4.0 migration created that. Under httptest.NewTestServer it becomes http://example.com as soon as anything uses the server, so http.Get(srv.URL) returned 200 with Server: cloudflare and IANA's Example Domain page — the test passing having asserted nothing. go-testing.md carries a tier-1 carve-out: a test never sends a request off this machine.
A leaked Rows wedges the single-writer pool forever. The corpus mandated SetMaxOpenConns(1) and never said defer rows.Close(), rows.Err() or defer tx.Rollback() anywhere. busy_timeout never fires because the wait is in Go's pool rather than in SQLite, and seven such defects on one file drew nothing from go vet or staticcheck -checks=all. Rules 8 and 9, plus the t.Cleanup on Stats().InUse that is the only thing which catches it mechanically.
LogValue protects only the top level, so a secret is now its own type carrying LogValue, String and MarshalText. make fmt never converged — go fix applies each fixer once and one rewrite exposes the next, and the import it adds to a single-import file is a second declaration gofmt keeps and only goimports merges.
Also: the WriteTimeout ladder is off by ReadTimeout (the canonical 10 s and 30 s ceiling a handler at 20 s, and past the deadline the client gets no status line while w.Write returns nil); the upload cap had no ReadTimeout half, so its tier-1 413 was unreachable; busy_timeout outranks the context deadline; the Go pin's note named one of five html/template escaper XSS advisories. Changes to the language had never been reviewed in four years of run logs — generic methods cannot implement a port interface, go fix's embedlit sets up a silent shadowing trap, and an iterator ignoring yield's result panics the caller's break.
Ten adversarial passes, 16 defects, the last two clean — every defect in the run's own new text, and four of them in the paragraph reporting the run. Reference: baseline-reference v4.6.0, 79 gates, make ci green.
v4.5.0 — the two decisions, decided
The two decisions v4.4.0 left open, decided.
encoding/json/v2 is the JSON package. A row in stack/go.md's Modern stdlib choices and the ruling under it: new code imports it; an untagged call site moves the first time a change touches it; a contract a tag promises — a CLI's -json at any tag, a library's wire format from v1 — moves at its next major, as a major; a web application's /api moves with the next deploy. Eight bullets say what changes at a call site, each run under the pin. go-http-client.md's fence reads with UnmarshalRead; go-cli.md, the cli-tool checklist, and the cli-tool project type name v2.
htmx 4.x waits for 4.0.1. The ban stays, with a trigger: 4.0.1 shipping starts a baseline release that adopts 4.x, and projects move with that release. The record carries what 4.x renames and removes for that release.
Also recorded: the omitzero fixer under 1.27.1 strips omitempty from a struct-typed field rather than upgrading it, so make check goes red until the tag is changed by hand.
17 review passes, 95 defects, the last two clean on both lenses. Reference: baseline-reference v4.5.0, 79 gates, make ci green.
v4.4.0 — the pin moves to Go 1.27.1
Go 1.27.1 is the pin; go.mod says go 1.27. The 1.27 rulings: httptest.NewTestServer for handler tests and inside a synctest bubble, synctest.Sleep, header limits at their defaults, Close drains (drainAndClose gone), uuid as a stdlib row, errors.AsType the rule with errors.As the named exception, encoding/json/v2 not adopted. htmx 4.0.0 is stable since 2026-08-28 and stays banned until adopted deliberately. Reference v4.4.0 (4d1ea3f) pins ce86f13: verify.sh 79 gates, exit 0; make ci green under go1.27.1. Sixty-nine review findings over sixteen passes, the last two clean.
v4.3.0 — no code before the brief
Every task carries four fields the user has seen before the first line of code — job, why, guardrails, done means — drafted by the agent, confirmed by the user, and re-read as the acceptance test. SKILL.md carries the rule; README The task brief is the definition. Every project keeps SPEC.md at its root as the project-level brief; the checklists box it. The budgets held by trimming rationale sentences, the branch the 2026-08-18 decision left open. Reference synced at 883dcb8: 78 gates, exit 0.
v4.2.0 — go fix joins the gates
go fix is a gate and a mutator, the way gofmt and goimports already were.
go fix -diff ./...is the third line ofcheck(red while a rewrite to current idiom is pending);go fix ./...is the last line offmt, after goimports, because it type-checks and manages its own imports.- The fixers ship with the toolchain, so the pin decides what the gate demands: 1.26.0–1.26.7 register the same 22, 1.27.0 registers 26. A newer Go on the machine can turn the gate red on idioms the pin never asked for — prefix
GOTOOLCHAIN. A moved major meansmake fmtfirst, its rewrite as its own commit. - A fix you judge wrong is switched off by name,
-<fixer>=false, on both lines, in the commit that says why. stack/go.mdloses theanyrule (the fixer enforces it) and gainserrors.AsTypein the errors row; three fences the tool would rewrite are rewritten (WaitGroup.Go,errors.AsType).- Reference synced and tagged v4.2.0:
verify.sh77 gates,make cigreen under Go 1.26.7. The gate was red on the tree it inherited — which is the finding.
Seven review rounds, two lenses each, twenty-seven distinct defects, the last two rounds clean. The record is in VERIFICATION.md.
v4.1.0 — gaps found in a project flow back to the baseline
The corpus could learn from projects and had no way to hear about it. Every rule in this repository was written by looking at the repository. Two things only a project can show — a decision the checklist has no section for, and a rule naming a dependency the standard library has since absorbed — were visible exactly once, to whoever hit them, and died with the task. Both now have somewhere to go, and neither needed a new document.
Project → baseline. SKILL.md Handing the work back gains one bullet: a baseline gap goes on the next-steps list. Two kinds count and nothing else — the checklist had no section and you settled it by inventing rather than by matching the surrounding code, or a baseline rule names a dependency stdlib now covers. Name it as a step and leave it: never edit the baseline as a side effect of project work. The bar is the whole design, because this rule's failure mode is noise rather than silence. A reflect-on-your-work rule with a soft trigger fires on every task and is skimmed past by the third one; both signals here are facts the agent already holds when it finishes, so neither costs a survey. "This looked reusable" is named and excluded.
Release → baseline. README.md Maintenance protocol gains Ask what the release absorbed: at every Go major (Feb/Aug), read the notes for what moved into the standard library, not only for what changed. A capability landing in stdlib usually moves a row out of Approved third-party dependencies in stack/go.md and into Modern stdlib choices, and rewrites the pattern that prescribed it in the same commit. A dependency whose justification has expired is a live defect against core value 1, and only two things go looking for one.
Absorption is a rewrite, not a retirement, and that had to be said where retirement is decided. A pattern whose dependency moves into stdlib still has something to say — the mechanism changes and the document stays. Only a pattern that existed solely to cover the gap meets signal 1. Without that paragraph, Retiring a pattern reads as a licence to delete a document the day Go absorbs its dependency, which is the moment its rules are most likely to be got wrong.
The rule was tested by looking for a gap, not by reading it. Inbound webhooks — the HMAC, the replay window, the raw body read before parsing — are genuinely uncovered by this corpus. CSV downloads look uncovered and are not: they are boxes under Taking a file from a user. The false guess is the point of the bar.
Nine defects over ten passes, plus two caught while writing the record, and the interesting ones were arithmetic. The first draft of the budget entry said none of the six patterns the 2026-08-18 raise bought had ever landed — one had, 9c0be4a, on the day the number moved. The same paragraph called 125 tokens per pattern a rate; the trigger sections measured since cost 61, 179 and 201, so it is an average nobody pays. Two more were off by a single token, because make tokens divides the whole path's bytes once while per-file deltas each round down. Every one came from carrying a number over from a draft instead of re-deriving it — the failure Waived budgets already names.
A judgement the record demanded is made out loud, and it is not a comfortable one. A third path this entry missed recorded that SKILL.md and VERSIONS.md sit inside both the floor and the change path with no per-document budget over either. This release grew SKILL.md by 116, the second such edit, and 380 in total since the raise — nearly half the 763 tokens it bought, in the one place no budget looks. Only 179 of it was ever a new pattern. The change path now stands at 7,939 of 8,000: 61 tokens, less than any trigger section ever measured. What would make these numbers wrong names the branch — trim, do not raise — and which trim, or the shape budget instead, is a decision that comes before the next pattern rather than with it.
One defect was found and deliberately not fixed. "Nothing else qualifies" could be read as scoping out Waivers and conflicts, which separately tells an agent an unresolvable rule collision is a defect in the baseline. The two have different mechanisms and different timing, and the clause that would join them costs about 15 of the last 61 tokens on the most-paid path. Spending the end of a budget to pre-empt a misreading, in the release that says the budget is spent, is the wrong trade. It is written down instead.
Gate closed before the tag. Reference 6239e4e tagged v4.1.0, pinning 99a94e0; GOTOOLCHAIN=go1.26.7 ./verify.sh exit 0 over 76 gates, and make ci green on that commit. SPEC.md was the only file that moved there, and that is the finding: both rules govern what an agent reports and what a maintainer does on the 90-day cycle, so neither is a property a repository can gate. A rules change the reference cannot gate is exactly the kind that gets tagged on a reading, so it was run rather than reasoned about.