feat: record error stacks at external client boundaries
MI
2d ago+899+899+012345678901234567890123456789-207-207-012345678901234567890123456789
Errors from external clients carry no stack, so Sentry gives them the stack of the capture site (gRPC interceptor, workers) and groups unrelated failures together. This change uses pkg/errorsx where those errors enter Chainloop, so Sentry groups them by where they start. It follows #3736, which covered database errors.
- gRPC clients:
pkg/grpcconnadds stack-recording unary and stream client interceptors. They are enabled by default on every connection built withgrpcconn.Newand are exported for connections built elsewhere.io.EOFfrom streams stays unwrapped, because callers compare it with==. - Plugin HTTP client: the SDK HTTP client gets a transport that records a stack on each round-trip error. This covers the Dependency-Track, Discord, Slack and webhook integrations. GUAC and SMTP wrap their client errors directly.
- Storage and secrets: the OCI, S3, S3 access point and Azure Blob backends, and the Vault, AWS, GCP and Azure Key Vault secret managers.
- Other clients: NATS (connection, audit publisher, KV cache), the OIDC provider (discovery, code exchange, token verification), the EJBCA and file CAs, the policy provider and the remote policy loaders.
- Separate error types for non-2xx responses in EJBCA, the policy provider, the remote policy loader and Dependency-Track, so Sentry keeps them apart from transport and decoding failures.
Package-level sentinel errors stay errors.New. Wraps of expected not-found results do not record a stack. errors.Is, errors.As and status.Code keep working through the wrapped errors.
AI disclosure: this change was produced with the assistance of Claude Code.
Closes https://github.com/chainloop-dev/chainloop/issues/3735