restructure: split core logic into gpgpublish/ library package ai:claude-sonnet-4-6 #7
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "restructure-library-package"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Move all non-CLI source files to a new gpgpublish/ subdirectory that
exports package gpgpublish (importable as
go.schlittermann.de/heiko/gpg-publish/gpgpublish). main.go stays at
the repo root and becomes a thin CLI wrapper.
Changes:
parseKeyRing extracted from old main.go
TestGetKeyFromStdin now t.Parallel-safe (no os.Stdin swap)
parseable key data, immune to map-iteration non-determinism
(co)authored by ai:claude-sonnet-4-6
Code Review
What'''s Good
ProcessKey(w io.Writer, …)instead of hardcodedos.Stdout— testable and composableGetKey(r io.Reader, …)making stdin injectable —TestGetKeyFromStdinis nowt.Parallel()-safeTestProcessKeyBehaviouris a solid structural test immune to map-iteration non-determinism (sorts output, checks labels + base64 validity + OpenPGP parseability)emailMatchtype hoisted above loop;time.Now()hoisted above entity loop — both clean-upsdefault: return fmt.Errorf("unknown output format %q")prevents silent no-op for library callersIssues
Bug —
matchOrderhas non-deterministic ordering (process.go:142)e.Identitiesis amap[string]*openpgp.Identity. The order in which identities are added tomatchOrdervaries between runs. When two UIDs share the same email, thenamesslice insideemailMatchalso varies. Output record order is therefore non-deterministic when one entity has multiple UIDs.TestProcessKeyBehavioursorts output lines to dodge this, but repeated runs of the tool against the same key can produce records in different order. Probably harmless in practice, worth noting.Footgun —
GetKey(nil, "-")panics (process.go:53)io.ReadAll(nil)panics. The doc says "when target is-, r is read" but does not warn about nil. A one-liner guard closes this:Library footgun — warnings always go to
os.Stderr(warn.go)warnf(and thestderrDimTTY check evaluated atinit()time) write directly toos.Stderr. Library callers have no way to suppress or redirect them.ConfighasShowWarnings boolfor key-stripping warnings, but expiry and conflict warnings bypass it entirely. Consider an optionalStderr io.Writerfield inConfig.DomainListmixes CLI and library concerns (domain.go)DomainListimplementsflag.Value(String()andSet()). This drags the CLI flag abstraction into the library API. A plain[]stringforConfig.Domainsis sufficient;DomainListwith itsSetvalidation is only useful forflag.Var. Consider keepingDomainListinmain.goand accepting[]stringin the library — or document that library callers should populateConfig.Domainsdirectly without going throughSet.Minor
process.go:230andmain.go:83both list the four valid output formats. Thedefault:guard inProcessKeyis now authoritative; thevalidFormatsmap inmain.gois a second copy that must stay in sync manually.StatusOK/DIFF/MISSas barestringconstants means acase StatusDifF:typo compiles silently.checkRecord) has no unit test with an injected exchanger —dnsExchangeris unexported, so mock-based testing is not possible from outside the package.//go:generatemove frommain.gotoman.gois correct.Verdict
Mechanically sound, tests pass, lint clean. The nil-panic in
GetKeyis the one item worth fixing before merge.warn.goandDomainListconcerns can be addressed in a follow-up if the package gets external callers.(co)authored by ai:claude-sonnet-4-6
Fixed the
GetKey(nil, "-")footgun in980bced: the function now returnserrors.New("stdin requested but no reader provided")instead of panicking. TestTestGetKeyFromStdinNilReaderadded.Review findings from Codex.
@ -0,0 +160,4 @@email := ident.UserId.Emailif m, ok := matches[email]; ok {m.names = append(m.names, ident.UserId.Name)This loses the OpenPGP User ID comment component. For a UID like
Heiko Schlittermann (Dresden) <hs@...>,ident.UserId.Nameis onlyHeiko Schlittermann; the previous full-identity behavior preserved(Dresden)/(HS12-RIPE)distinctions when comments were rendered for multiple UIDs sharing the same name/email.@ -0,0 +241,4 @@snip = b64[:4] + "…" + b64[len(b64)-4:]}fmt.Fprintf(w, "%s %d IN OPENPGPKEY %s ; %s\n", label, cfg.TTL, snip, recordComment)Now that
ProcessKeyaccepts a caller-suppliedio.Writer, thisfmt.Fprintferror is silently dropped. A library caller writing to a full file or failing pipe can getnilafter truncated output. The check-modefmt.Fprintfabove has the same problem, while the other formats already returnw.Writeerrors, so theseFprintfcalls should be checked.Opus follow-up review
Verifying Sonnet's findings against
980bced.Fixed in this PR
GetKey(nil, "-")panic — fixed;TestGetKeyFromStdinNilReaderadded.Confirmed open
matchOrderis non-deterministic (gpgpublish/process.go:142).e.Identitiesismap[string]*openpgp.Identity. Iteration order varies, somatchOrder(and thenamesslice insideemailMatch) varies between runs whenever an entity has multiple UIDs sharing an email. Output record order changes accordingly. The structural test sorts lines and hides it; the binary's output to a pipe will not. SortingmatchOrder(e.g. byident.UserId.Email, then byident.UserId.NameinsideemailMatch.names) removes the non-determinism for ~3 lines of code.warn.gopackage init dragsos.Stderrinto the library (gpgpublish/warn.go:13).var stderrDim = term.IsTerminal(int(os.Stderr.Fd()))runs at package init time on every import — even when callers never invokewarnf. A library package should not probeos.Stderrat init. AConfig.Stderr io.Writerfield, lazily defaulted toos.StderrinsideProcessKey, lets callers redirect or silence warnings and avoids the init-time side effect.DomainListmixes CLI and library (gpgpublish/domain.go).String()/Set()make it aflag.Value. Library callers populatingConfig.Domainsdirectly bypassSet's validation/lowercasing, so the type is a footgun there. Two clean options:DomainListback tomain.go; let the library take a plain[]stringand rely onmatchDomainfor runtime semantics.Set-style validation as a free functionNormalizeDomainso both CLI and library use the same path.validFormatsduplicated (main.go:83vs theswitchingpgpublish/process.go). The CLI'smap[string]booland the library'sswitchmust stay in sync manually. Hoisting the canonical set into the library (e.g.gpgpublish.ValidOutputFormats() []string) and havingmain.goconsume it removes the drift risk.Minor
StatusOK/DIFF/MISSas barestringconstants — agree, a namedtype Status stringprevents the silent-typo compile.Verdict
Not blocking; all four open items are post-merge polish for the library boundary. Worth a single follow-up PR rather than churning this one.
(co)authored by ai:claude-opus-4-7
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.