Go Code
July 30, 2026 · View on GitHub
Mint function: The mint Cloud Function source lives in two places that must stay in sync:
internal/mint/main.go— the source of truth (has its owngo.mod, tests run frominternal/mint/)internal/dispatch/gcf/mintsrc/main.go.embed— the embedded copy deployed as a GCP Cloud Function
When changing internal/mint/main.go, always copy it to internal/dispatch/gcf/mintsrc/main.go.embed. If go.mod or go.sum changed, sync those to go.mod.embed and go.sum.embed too.
Standalone mint: cmd/mint/ is a standalone HTTP server variant of the token mint that serves the same purpose as the GCF mint (internal/mint/) but runs without GCP infrastructure. Both use the shared internal/mintcore/ library for token minting logic; they differ only in deployment model (filesystem PEM vs Secret Manager, JWKS vs STS verification). It supports custom role permissions via CUSTOM_ROLE_PERMISSIONS and a fallback proxy to an upstream mint. It has its own go.mod and tests run from cmd/mint/.
CF Worker adapter: internal/dispatch/cf/workersrc/ is a thin TypeScript Cloudflare Worker adapter that consumes mintcore via WASM (cmd/mint-wasm). The adapter handles I/O only (Worker secrets, host fetch, Fetch Request/Response mapping); all mint logic stays in Go. The Go WASM bridge registers mintcoreInitMint and mintcoreHandleFetch on globalThis via syscall/js; changes to these entry points in cmd/mint-wasm or to the contracts they consume in internal/mintcore/ require updating workersrc/src/index.ts to match.
Mint client: internal/mintclient/ is the Go client for calling the mint service at runtime. It exchanges a GitHub Actions OIDC JWT for a role-scoped installation token. Unlike internal/mint/ and internal/mintcore/, it has no embedded copies or sync requirements.
The internal/mintcore/ module is shared between the mint and devmint. Its files are also embedded for Cloud Function deployment at internal/dispatch/gcf/mintsrc/mintcore/*.embed. When changing any file in internal/mintcore/, sync it to the corresponding .embed file under mintsrc/mintcore/. Note: the mint's go.mod.embed uses replace mintcore => ./mintcore (not ../mintcore), because provisioner.go rewrites the replace directive at bundle time to match the deployed directory layout.
When adding a new file to internal/mintcore/:
- Create the
.embedcopy: Place it ininternal/dispatch/gcf/mintsrc/mintcore/(required for all files —lint-mint-embed-syncenforces this). - Register in
embeddedMintFiles: If the file will be included in the GCF bundle — either no build tag (e.g.,config.go) or//go:build !js(e.g.,sts_verifier.go,gcp_pem.go,wif.go) — add it toembeddedMintFilesininternal/dispatch/gcf/provisioner.goand to thego:embeddirective. - Add to
gcfSkip: If the file should NOT be in the GCF bundle — Worker-only files (//go:build js) or standalone-mint-only files — add it to thegcfSkipmap inTestEmbeddedMintSource_MatchesOriginalinprovisioner_test.goinstead ofembeddedMintFiles. The three current entries arefetch_js.goandpem_js.go(Worker-only,//go:build js) andfile_pem.go(standalone-mint-only,//go:build !js).
Dispatch workflows: See Workflow Contracts for dispatch sync rules, secret/input threading across installation-mode chains, and review instructions.
Interface documentation: When extending a Go interface with new methods (e.g., adding methods to ci.Driver in pkg/behaviourtest/drivers/ci/driver.go), check docs/guides/dev/ for documentation that lists or enumerates the interface's methods (e.g., behaviour-drivers.md). If found, update the method list to include all current methods, not just the newly added one. The lint-interface-doc-sync pre-commit hook enforces this for ci.Driver.
When making changes to Go code under cmd/ or internal/:
- Unit tests: Run
make go-test(orgo test ./...) and fix any failures before committing. - Coverage: CI enforces thresholds via Codecov (see
.codecov.yml). Patch coverage on changed lines must meet 80% (with a 5% tolerance). Project coverage must not drop more than 1% below the base branch.make go-testruns tests with-coverlocally but does not enforce these thresholds — a PR can still fail the Codecov status check if new or changed code lacks tests. Add or extend_test.gofiles for logic you introduce or modify. - Vet: Run
make go-vetto catch common issues. - E2E tests: Run
make e2e-testif your changes touchinternal/appsetup/,internal/forge/,internal/cli/, orinternal/layers/. These tests exercise the full admin install/uninstall flow against live GitHub pool orgs using mint/OIDC authentication.
Concurrency testing (race detection)
make go-test runs all tests with -race. Every test must pass under the race detector.
When to write a race test
When a type is shared across goroutines — for example, via World.Clone in the behaviourtest framework — write a dedicated race_test.go in the type's own package to verify thread-safety. The race detector can only catch bugs if the test exercises real concurrent access on mutable state.
Pattern: real types with forge.NewFakeClient()
Construct the real driver type backed by forge.NewFakeClient(), not a synthetic stub. Seed the FakeClient so all methods return immediately (no network, no polling). Then launch concurrent goroutines exercising representative methods and rely on -race to detect unsynchronized access.
func TestConcurrentAccess(t *testing.T) {
t.Parallel()
fc := forge.NewFakeClient()
// Seed FakeClient so methods return without errors.
fc.FileContents = map[string][]byte{
"org/repo/dummy.yaml": []byte("content"),
}
d := New(fc) // construct the real driver type
ctx := context.Background()
const goroutines = 12
var wg sync.WaitGroup
for range goroutines {
wg.Add(1)
go func() {
defer wg.Done()
// Exercise representative methods concurrently.
_, _ = d.GetFileContent(ctx, "org", "repo", "dummy.yaml")
_ = d.CommitFile(ctx, "org", "repo", "path.txt", "msg", []byte("data"))
}()
}
wg.Wait()
}
Convention: use 12 goroutines. This is high enough to trigger races reliably but low enough to avoid resource exhaustion in CI. See pkg/behaviourtest/drivers/scm/github/race_test.go for the canonical example.
Assertions inside goroutines: assert not require
Inside goroutines spawned by a test, use assert.XXX (testify/assert), not require.XXX (testify/require). require calls t.FailNow(), which calls runtime.Goexit(). Go's testing package documents that FailNow must be called from the goroutine running the test function, not from other goroutines — calling it from a spawned goroutine violates this contract and can silently mispass the test or crash the process. assert calls t.Errorf(), which is safe from any goroutine.
go func() {
defer wg.Done()
tok, err := d.AccessToken(ctx)
assert.NoError(t, err) // safe from any goroutine
assert.Equal(t, "expected", tok)
// require.NoError(t, err) — never use require inside a goroutine
}()
This applies to all require functions (require.NoError, require.Equal, require.NotNil, etc.) — the entire require package uses FailNow internally. The test goroutine itself (the function passed to t.Run or the Test* function) can use require normally.
Why synthetic stubs don't work
Stubs that implement an interface with no-ops or stateless pass-throughs hold no mutable state, so the race detector has nothing to detect. Even stubs that use atomic.Int64 counters are invisible to -race because atomics are correctly synchronized by definition. The point of a race test is to exercise the real type's fields — only a real constructor backed by a thread-safe fake can trigger the detector on unsynchronized production code.
Running the fullsend CLI
Audience: contributors and agents working from a repo checkout. Do not
change end-user or operator guides under docs/guides/getting-started/,
docs/guides/user/, or docs/guides/infrastructure/ to require go run —
those audiences install a released fullsend binary.
When agents (or humans working from this checkout) need the fullsend CLI, invoke it from the repo root with:
go run ./cmd/fullsend <subcommand> …
Do not use a preinstalled fullsend from mise, $PATH, GOBIN, go install, or another clone. Those binaries often lag the branch you are on. Enrollment and other mint-mutating commands rewrite Cloud Run env vars; a stale CLI can apply obsolete merge logic against the hosted mint.
This already happened: an enrollment for crc-org/crc used a June-era mise fullsend that still wrote org-scoped ROLE_APP_IDS keys and re-derived ALLOWED_ROLES from slash-keyed entries only. Shared role-only keys such as e2e and fix were ignored, so the mint dropped those roles from ALLOWED_ROLES and e2e broke until the mint was restored. Current tree enroll is safer, but the durable fix for agents is to always run the CLI from source.
make go-build / bin/fullsend is fine when you intentionally build this checkout first. Prefer go run unless you have a reason to keep a built binary.
Running e2e tests
The e2e tests mint short-lived GitHub App installation tokens via the central token mint. Pool-org admin operations use mint/OIDC in CI and do not require a dedicated mint URL secret.
- CI (mint): Uses the hosted public mint (same default as
fullsend admin --mint-url) with the workflow's OIDC identity. The e2e workflow exchanges the OIDC JWT for ane2e-role installation token on the pool org. Override withFULLSEND_MINT_URLif needed. - Local: Run
gh auth login(or setGH_TOKEN/GITHUB_TOKENwith pool-org admin access). Mint usesFULLSEND_MINT_URLor the hosted default.
Do not increase e2e or behaviour suite timeouts without explicit human authorization in the current session (or an issue/PR comment that clearly authorizes that bump). Suite ceilings are job timeout-minutes in .github/workflows/e2e.yml for the e2e / behaviour jobs, the matching go test -timeout values in the e2e-test / behaviour-test Make targets, and the default E2E_LOCK_TIMEOUT. Scenario-level wait or assertion windows (for example dispatch detection) are out of scope when an issue asks for them — those are not suite ceilings. On timeout failures, diagnose and fix the root cause (slow scenarios, unnecessary waits, lock contention); do not open a PR whose primary change is raising the suite timeout.
When reviewing PRs: Flag unauthorized suite-timeout increases as an important-severity finding (policy violation). Explicit human authorization in the linked issue or a PR comment is the only exception.
See docs/guides/dev/e2e-testing.md and make help for pool org setup and troubleshooting.