chore(auth): Validator interface and registry seam
📝 Summary
Lands Step 2 of the S08 authentication-stub plan: the foundational seam under internal/auth/ that the remaining steps consume. No middleware, no dispatch table, no handlers — only the contract surface.
internal/auth/auth.go—Scheme(string-typed:SchemeBearer = "Bearer",SchemeBasic = "Basic"), theValidatorinterface (Validate(ctx, secret.Secret) error), sentinel errorsErrTokenMissing/ErrTokenInvalid, and theServiceName = "artifact-registry"constant.internal/auth/registry.go—ValidatorFactorytype plus the package-levelRegister/MustResolveseam.MustResolvepanics on three contract violations: no factory registered (the build-tag fence's runtime backstop per S08 spec section Build-tag fence), the factory returns an error, and the factory returns a nilValidatorwith a nil error.Registerpanics on double-register or nil factory.internal/auth/authtest/fake.go—Fake(tb testing.TB, tok string) Validatortest helper using plain string equality. Callstb.Fatalfwhentokis empty so a caller that builds Fake from a zero-valued struct field fails loudly at the call site instead of silently authenticating an empty credential. Used by downstream middleware, handler, and bootstrap tests; constant-time semantics belong in the production validator.scripts/check-no-authtest-in-prod.sh+.gitlab-ci.ymljoblint:no-authtest-in-prod— static fence rejecting non-test imports ofinternal/auth/authtest, soFake.Validate's plain==cannot reach a production binary.
The 404 fallback and the 405 response in the dispatch middleware and /v2/auth/token handler will call internal/transport.WriteError (shipped via !261 (merged)) directly, so this MR no longer ships its own envelope writer.
Spec: S08 Authentication (stub). The plan's Step 2 entry has the full file list.
This step is a sibling-parallel enabler: the dispatch middleware, the /v2/auth/token handler, and the bootstrap validator each import this seam without depending on one another.
📏 MR size
| Category | Files | Lines | Share | Review burden |
|---|---|---|---|---|
Hand-written Go production (auth.go, registry.go, authtest/fake.go) |
3 | +237 | 31% | High — core seam |
Hand-written Go tests (*_test.go) |
3 | +382 | 50% | High — covers every panic + behavior bullet from the plan's Acceptance, plus the unwrap-contract, panic-message-substring assertions, nil-Validator guard, wire-token literals, and empty-tok Fatalf contract added during review |
CI fence (scripts/check-no-authtest-in-prod.sh, .gitlab-ci.yml) |
2 | +56 | 7% | Low |
Plan housekeeping (docs/plans/2026-05-12-authentication-stub.md) |
1 | +93 / -42 | 12% | Low — Step 2/3/4 entries aligned with what shipped |
| Total | 9 | +768 / -42 | 100% |
Headline: ~50% of the diff is tests against the seam — every panic condition on Register / MustResolve (with substring assertions on the recovered value), the sentinel %w + errors.Is unwrap contract, and the Fake's match/mismatch/edge cases.
📦 Commits in this MR
Squashed on merge. Pre-squash history (most recent first):
| Order | Commit | Purpose |
|---|---|---|
| 9 | chore(auth): address S08 Step 2 second-round review feedback |
Second pass folding in the second-round blocking threads (fence hardening, nil-Validator guard in MustResolve, Fake(tb testing.TB, ...) signature, wire-token literal pins, ServiceName tense, plan-ref removal across the package) plus a self-review polish (defensive return nil after Fatalf, compile-time Validator interface checks) |
| 8 | chore(auth): address S08 Step 2 review feedback |
First pass folding in the initial blocking threads, the Duo recommendations, and the AppSec advisory |
| 7 | docs(plans): record !266 in S08 Step 2 Status row |
Plan housekeeping |
| 6 | docs(plans): list Step 2 test files in S08 auth-stub plan |
Plan housekeeping — mirrors what shipped |
| 5 | refactor(auth): drop "returned error" from MustResolve panic message |
Aligns the panic phrasing with go-style.md error-context guidance |
| 4 | docs(auth): drop stale post-S01-merge framing from seam doc comments |
Doc-comment tidy after S01 transport merged |
| 3 | refactor(auth): simplify Step 2 seam per code-simplifier pass |
Post-implementation simplification pass |
| 2 | chore(auth): Validator interface and registry seam |
Implementation that satisfies the tests below |
| 1 | test(auth): Validator interface and registry seam |
Tests authored first (panic-skeleton pattern from the implement-step skill) |
🧪 Test plan
- CI green on
go test ./internal/auth/...(registry panics withauth:-prefix substring assertions for missing-factory, double-register, nil-factory, factory-error-surfaces, and nil-Validatorcases; sentinel%w+errors.Isunwrap contract; sentinel-error distinctness;Schemeliteral wire-token pins; Fake match / mismatch / prefix / suffix coverage; empty-tokFatalfcontract via a recording TB). - CI green on
golangci-lint(includingtestifylint), the newlint:no-authtest-in-prodjob, and the pre-commit hook stack (go-fmt,go-imports,go-mod-tidy,shellcheck,shfmt). - Reviewer sanity: imports resolve only against stdlib, LabKit
v2/secret,testify, and the auth package itself.
🔗 Related
- Tracking work item: #96 (closed)
- Plan: docs/plans/2026-05-12-authentication-stub.md
- Spec: docs/specs/S08-authentication.md
- Previous step (merged): !256 (merged)