Release v0.63.0
Date: 2026-04-11
Theme
SDK coverage initiative — Stage 2: the Go SDK ships.
v0.62.0 landed Stage 1 (the .NET SDK) and the generator-driven SDK track. v0.63.0 adds Go as the second language on that track, using the same orchestration (pnpm sdk:generate), the same committed-output pattern, the same --check determinism gate, and the same smoke-test shape. What’s new is the publishing model: Go modules don’t have a central registry like NuGet or PyPI — “publishing” means creating a git tag that proxy.golang.org can discover.
Stage 3 (Java) is still ahead. See docs/initiatives/sdk-coverage-stage1.md for the condensed execution checklist and the running deviation log across all stages.
Highlights
Go SDK at github.com/TroyCoderBoy/pulp-engine/packages/sdk-go
- Package:
packages/sdk-go/. Import pathgithub.com/TroyCoderBoy/pulp-engine/packages/sdk-go. Requires Go 1.21 or later. - Generated from:
openapi.jsonvia the openapi-generatorgotemplate (JAR7.10.0, pinned in openapitools.json alongside the .NET pin). - Install:
go get github.com/TroyCoderBoy/pulp-engine/packages/sdk-go@v0.63.0 - All generated source is committed. Same pattern as the .NET SDK. Debuggable, reviewable, reproducible.
- Hand-written files (README, RELEASING, generator config, version.go, health_smoke_test.go) protected by
.openapi-generator-ignore. Verified by regenerating twice with a sentinel edit inREADME.mdbetween runs. - Round-trip smoke test
health_smoke_test.goexercises the generatedHealthAPIService.HealthGet()against a local API, asserts the typedHealthGet200Responseshape, and verifies the SDK’sVersionconstant fromversion.gomatches the API’s runtimeversionfield as a lockstep-integrity check. Passed end-to-end on this release commit (docker run --network host golang:1.23-alpine go test -run TestHealthSmoke).
Go subdirectory module publishing — tag packages/sdk-go/vX.Y.Z
Because the Go SDK lives at packages/sdk-go/ inside the monorepo rather than in its own repo, Go’s subdirectory module support handles the import via a tag-prefix convention: the sibling tag packages/sdk-go/v0.63.0 (with the slash prefix — git allows it) makes the subdirectory installable as a standalone module. Consumers pay the ugly import path once in their import block; everything else just works.
This is the single highest-risk step in the initiative. Git tags are immutable and the Go module proxy caches aggressively, so the pre-flight dry run documented in RELEASING.md is mandatory.
⚠ Private-repo caveat — consumer setup required
Surfaced during Stage 2 execution, not in the original plan: the pulp-engine repository is currently private, and proxy.golang.org (Go’s default module proxy) cannot fetch from private repos. The subdirectory-tag scheme still works — consumers can go get the SDK — but they must first configure GOPRIVATE and provide authenticated git credentials:
# One-time setup per consumer machine:
go env -w GOPRIVATE=github.com/TroyCoderBoy/*
# Then either SSH (recommended):
git config --global url."git@github.com:".insteadOf "https://github.com/"
# ... ensuring your SSH key has read access to TroyCoderBoy/pulp-engine
# OR HTTPS + PAT: store a GitHub PAT with `repo` scope via git credential manager
# Finally:
go get github.com/TroyCoderBoy/pulp-engine/packages/sdk-go@v0.63.0
This is real consumer friction. It’s strictly worse than the .NET story (NuGet public package) and the Python story (PyPI public package). If the repo ever becomes public, none of this setup is needed — consumers just go get through the public proxy and everything works. Until then, the README’s one-time setup section is mandatory reading for any new Go consumer.
Workflow adjustment: the originally-planned publish-sdk-go.yml step that primes the Go module proxy via go mod download -x has been replaced with a git ls-remote origin verification step and a log message printing the consumer-install snippet. Proxy priming is pointless for private repos — the proxy will always 404.
Pre-flight dry-run adjustment: the runbook verification-via-proxy step doesn’t work for private repos. The updated RELEASING.md documents both the current (private-repo) verification path — git ls-remote origin <subdir-tag> plus an optional end-to-end consumer check on an authenticated second machine — and the future (public-repo) proxy-path verification to restore if/when the repo becomes public.
Stage 2 verification completed via:
- A throwaway
packages/sdk-go/v0.63.0-pre0.<timestamp>tag was pushed and confirmed visible on origin viagit ls-remote. go mod download -xagainstproxy.golang.orgcorrectly escaped the path (%21troy%21coder%21boy) and then 404’d exactly as expected for a private repo — proving the proxy path is broken for this repo, which is why the workflow/runbook were adjusted.- The throwaway tag was deleted from origin before proceeding to the production tag push.
Two generator-limitation workarounds landed in scripts/generate-sdks.ts
The Go template surfaced two config pain points that Stage 1 (csharp) didn’t hit. Both are solved by a new per-language post-processing hook in the orchestrator:
go.modmodule path rewrite. The Go generator has no config key for the module path — it derives it asgithub.com/{--git-user-id}/{--git-repo-id}, which gives usgithub.com/TroyCoderBoy/pulp-engine, missing the/packages/sdk-gosuffix. The orchestrator rewrites themoduleline ingo.modafter generation. A regex-anchored match (/^module\s+\S+/m) keeps the operation deterministic. If a future openapi-generator JAR adds a nativemoduleNameconfig key, swap the workaround for that.version.goinjection. Go modules are versioned by git tags rather than by a file, but the lockstep check needs a readable “what does this SDK think its version is” source. The orchestrator writes aversion.gowith aconst Version = "X.Y.Z"declaration on every regeneration, driven by the workspace root version. The file is protected from generator regeneration by.openapi-generator-ignoreso only the orchestrator’s post-processor touches it.
scripts/check-version.mjs — Go reader with go.mod path verification
New readGoVersionConstant() reader parses the const Version = "..." from packages/sdk-go/version.go AND verifies the go.mod module line matches the pinned subdirectory path github.com/TroyCoderBoy/pulp-engine/packages/sdk-go. The second check is critical: if the post-processing hook is ever bypassed or silently fails, a generator regression could emit github.com/TroyCoderBoy/pulp-engine and silently break go get for consumers. Catching this at check-version.mjs time halts the release before the tag is pushed.
Lockstep check now spans eight package sites: root, api, editor, preview, sdk-typescript, sdk-python, sdk-dotnet, sdk-go.
.github/workflows/publish-sdk-go.yml — 3-job workflow
Modelled on publish-sdk-dotnet.yml with one critical difference: instead of pushing to a remote registry, the final step creates the sibling subdirectory tag and primes the Go module proxy cache.
check-ci— same CI-gate shape as the .NET and Python workflows. Waits forci.ymlto pass on the release commit withbranch=main,event=push. Timeout 25 min.build-and-smoke— runspnpm sdk:generate --check,go build ./...,go vet ./..., then starts the API in the background from the release commit’s own build (not aservices:container — same chicken-and-egg reasoning as .NET), waits for/health/ready, runsgo test -v -run TestHealthSmoke ./..., kills the API withif: always().publish-tag— createspackages/sdk-go/vX.Y.Zpointing at the release commit and pushes it. Idempotent: if the sibling tag already exists at the same commit, no-op. If it exists at a different commit, the job fails loudly (tag immutability means the operator has to investigate, not force-push).- Post-publish proxy priming —
GOPROXY=https://proxy.golang.org go mod download -xagainst the new tag, greps the-xlog for a proxy cache hit, emits a warning (not an error) if the proxy hasn’t indexed yet. Consumers hitting “unknown revision” briefly after release is not a failure; the proxy catches up within a few minutes.
CI freshness gate extends to Go
The pnpm sdk:generate --check step in .github/workflows/ci.yml (added in Stage 1) now checks both the .NET and Go SDKs on every PR. Any edit to openapi.json that would regenerate differently into either committed tree fails CI, forcing the developer to commit the regenerated diff.
Operator-visible changes
| Item | v0.62.0 | v0.63.0 |
|---|---|---|
| .NET SDK | dotnet add package PulpEngine.Sdk | Same |
| Python SDK | pip install pulp-engine | Same |
| TypeScript SDK | @pulp-engine/sdk (workspace) | Same |
| Go SDK | Recipe only in sdk-generation-guide | go get github.com/TroyCoderBoy/pulp-engine/packages/sdk-go@v0.63.0 |
| Lockstep check packages | 7 | 8 |
pnpm sdk:generate targets | --dotnet | --dotnet, --go |
| CI freshness gate | .NET only | .NET + Go |
Deliberate deferrals (carried forward from Stage 1)
- No per-endpoint tests. Stage 2 ships the same
/healthround-trip as Stage 1 did for .NET. If the smoke test catches a real regression in either SDK, that’s the signal to invest further. - Hand-written ergonomic layers on top of the generated clients stay out of scope.
- Independent SDK versioning cadence stays out of scope. The Go tag-immutability + proxy-caching constraints make a broken Go publish especially expensive (fix requires a full monorepo patch release). The escape hatch is documented prominently at the top of packages/sdk-go/RELEASING.md.
Known residuals
- Publish workflow still blocked by GitHub Actions billing. Same block that held v0.61.0’s PyPI publish and v0.62.0’s .NET publish. v0.63.0’s .NET + Go + Python publishes will all hit the same billing gate on tag push. None of this is a code problem — the moment billing is resolved, all three workflows re-run cleanly via
workflow_dispatch. - Go pre-flight dry run happens on the release machine, not in CI. The mandatory steps in RELEASING.md require creating and deleting a throwaway tag on the real
origin, which is an operator action. CI runs the build + smoke test, but not the proxy-path verification with a live tag. - Pre-existing flaky tests in the
apps/apisuite under full-suite load (documented inproject_flaky_tests_v1.md). All pass in isolation. Not a v0.63.0 regression.
Verification done on this commit
✅ pnpm sdk:generate (both .NET and Go regenerate)
✅ pnpm sdk:generate --check (determinism gate, both SDKs)
✅ docker run golang:1.23-alpine go build ./... (BUILD_OK)
✅ docker run golang:1.23-alpine go vet ./... (VET_OK)
✅ docker run golang:1.23-alpine go test (test file compiles)
✅ go test -run TestHealthSmoke vs local API (1 PASS, 12ms, version matches)
✅ .openapi-generator-ignore sentinel (README) (survived regeneration)
✅ node scripts/check-version.mjs (8 packages OK; tag mismatch is pre-commit expected)
✅ Subdirectory-tag scheme dry run (throwaway packages/sdk-go/v0.63.0-pre0.<ts> created,
visible on origin via git ls-remote, deleted cleanly)
⚠ go mod download -x via proxy.golang.org (404 as expected for private repo — documented, workflow
adjusted to use git ls-remote verification instead)