Closes Top-10 fix#4 of the 2026-05-03 issuer-coverage audit (see
cowork/issuer-coverage-audit-2026-05-03/RESULTS.md). Pre-fix, both
adapters had only happy-path test coverage with a single generic
ServerError pair each. Cloud CAs are typically the first-deployed
issuer in enterprise pilots; their diligence reviews dig hard into
IAM-error / cloud-error coverage. This commit lands the contract
tests.
AWSACMPCA — 5 tests in awsacmpca_failure_test.go. Each injects a
typed AWS SDK v2 error via the existing mockACMPCAClient seam and
asserts (1) error non-nil, (2) errors.As against the SDK's typed
value succeeds (so the wrap chain through fmt.Errorf("...%w", ...)
is intact), and (3) operator-actionable substring is present.
1. Issue_AccessDenied — *smithy.GenericAPIError with
Code="AccessDeniedException" (the SDK does NOT generate a
typed *types.AccessDeniedException; AWS uses the smithy
APIError shape for IAM denials). Asserts ErrorCode +
"not authorized" + IAM resource path preserved through wrap.
2. Issue_ResourceNotFound — *types.ResourceNotFoundException
names the missing CA ARN.
3. Issue_Throttling — *smithy.GenericAPIError with
Code="ThrottlingException", Fault=FaultServer. Asserts the
retryable class (FaultServer) is preserved through wrap so
upstream retry logic can engage.
4. Issue_MalformedCSR — *types.MalformedCSRException is terminal
(operator must fix the CSR, not retry); asserts the
validation-issue substring survives.
5. Issue_RequestInProgress — *types.RequestInProgressException
wraps cleanly; classification (retry vs reissue) is upstream's
responsibility per the spec's "no new retry logic" rule.
GoogleCAS — 5 tests in googlecas_failure_test.go. The adapter uses
stdlib net/http directly (NO Google Cloud Go SDK dependency in
googlecas.go), so SDK typed-error assertions don't translate. Each
test runs an httptest.Server that returns the canonical Google API
JSON error envelope:
{"error":{"code":N,"message":"...","status":"<STATUS>"}}
and asserts (1) error non-nil, (2) operator-actionable substring,
and (3) the canonical status string ("PERMISSION_DENIED",
"NOT_FOUND", "UNAVAILABLE") survives the wrap chain so upstream
classification can branch on it.
1. Issue_PermissionDenied — 403 / PERMISSION_DENIED; surfaced
error names the IAM resource path.
2. Issue_CAPoolNotFound — 404 / NOT_FOUND; surfaced error names
the missing pool resource.
3. Issue_OAuth2TokenRefreshFailure — token endpoint returns 401
invalid_grant; surfaced error mentions "token" so an operator
reading the log immediately distinguishes a credential failure
(rotate SA key) from a CA-side error (fix IAM binding). Test
also asserts the CAS endpoint is NOT reached when the token
exchange fails.
4. Issue_RegionalAPIUnavailable — 503 / UNAVAILABLE; surfaced
error preserves the retryable class markers (status code +
UNAVAILABLE string) for upstream retry classification.
5. Revoke_PermissionDenied — adapter does NOT silently swallow
the failure; pin the contract so the audit-row atomicity
guarantee from Bundle G (which lives in the service-layer
wrapper, not the adapter) continues to apply. Test also
verifies the revoke endpoint was actually reached, guarding
against a future regression that short-circuits before the
HTTP call.
Coverage delta:
awsacmpca: 71.0% → 71.0% (failure tests reuse existing wrap
code paths; behaviour-pin contract tests, not coverage tests).
googlecas: 83.4% → 84.4% (+1.0pp).
go.mod: smithy-go moved indirect → direct, since the new AWSACMPCA
test file imports it. CI's go-mod-tidy-drift gate enforces this.
Test-only commit. No production code changes.
Verified locally:
- gofmt clean.
- go vet ./internal/connector/issuer/awsacmpca/...
./internal/connector/issuer/googlecas/... clean.
- go test -short -count=1 ./internal/connector/issuer/... green.
- go test -race -count=10 ./internal/connector/issuer/awsacmpca
./internal/connector/issuer/googlecas green.
Audit reference: cowork/issuer-coverage-audit-2026-05-03/RESULTS.md
Top-10 fix#4.
Mechanical reformat. The new 'gofmt drift' CI step (added in
ci-pipeline-cleanup Phase 4, commit 71b2245) surfaced 111 files
with accumulated gofmt drift across cmd/, internal/, and deploy/test/.
Each file's diff is gofmt-standard: whitespace adjustments, intra-
group import sorting (alphabetical by import path within blank-line-
separated groups), and struct-tag column alignment. No semantic
changes — verified via 'git diff --ignore-all-space' which shows only
the line-position deltas from import reordering.
The gate stays in place after this commit. Going forward it catches
gofmt drift at PR time.
Closes M-001 partially; M-002, M-003, and CI threshold raise #2 deferred.
Stubs coverage shipped across 8 issuer connectors via per-connector
<conn>_stubs_test.go (~50 LoC each) pinning the not-supported
issuer.Connector interface methods (GenerateCRL, SignOCSPResponse,
GetCACertPEM, GetRenewalInfo). Most CAs delegate CRL/OCSP/CA-cert
distribution to managed services, so these are documented stubs that
return errors. Pinning them ensures the stubs aren't silently replaced
with no-ops in a future refactor.
Coverage delta:
digicert: 79.3% -> 81.0% (+1.7pp)
ejbca: 75.8% -> 76.5% (+0.7pp)
entrust: 70.8% -> 70.8% (stubs already covered)
sectigo: 78.0% -> 79.4% (+1.4pp)
vault: 81.0% -> 84.1% (+3.1pp)
openssl: 76.9% -> 78.0% (+1.1pp)
googlecas: 81.0% -> 83.4% (+2.4pp)
globalsign: 75.9% -> 78.2% (+2.3pp)
(awsacmpca not included; its 0%-coverage hotspots are stubClient methods
structurally different from the others' interface stubs. Already at 83.5%.)
Why the gates aren't yet met: the stub functions are tiny (1-2 lines
each, mostly 'return nil, fmt.Errorf("not supported")'). Lifting each
connector to >=85% requires per-connector failure-mode test files
mirroring Bundle J's ACME pattern (httptest.Server + canned 401/403/
429+Retry-After/5xx/malformed responses against the actual API methods).
That's ~200-300 LoC x 9 connectors = ~2000-2700 LoC of bespoke per-CA
mock work; exceeds this session's budget. Tracked as follow-on
Bundle N.A-extended / N.B-extended.
Deferred sub-batches:
N.C (M-002 + M-003): internal/service (70.5%) + internal/api/handler
(79.4%) round-out NOT YET STARTED. Tracked as Bundle N.C-extended.
N.CI (CI threshold raise #2): prescribed raises require underlying
coverage at proposed floors first. Premature raise would fail CI
immediately. Tracked as Bundle N.CI-extended.
Verification:
go vet ./internal/connector/issuer/{8-pkgs}/... clean
gofmt -l clean
go test -short -count=1 PASS for all 8
Audit deliverables:
gap-backlog.md: M-001 partial-strikethrough with per-connector table
+ Bundle N closure-log entry covering all 4 sub-batch statuses
closure-plan.md: Bundle N [~] with per-sub-batch status breakdown
CHANGELOG.md: [unreleased] Bundle N entry
Google Cloud Certificate Authority Service integration via REST API
with OAuth2 service account auth (JWT→access token). Synchronous
issuance model, CA pool selection, mutex-guarded token caching,
revocation with RFC 5280 reason mapping. No Google SDK dependency —
all stdlib. 19 tests with httptest mock OAuth2 + CAS API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>