feat(config): add StorageConfig proto and startup validation (S06 Step 10)

What

Adds the StorageConfig proto message and all sub-messages, plus the Go runtime view and startup cross-field validation. This is Step 10 of the Storage Layer (S06) plan, sibling-parallel and gating the driver-adjust steps (16, 17, 20) that construct typed drivers.

  • Proto (proto/artifactregistry/config/v1/config.proto, regenerated config.pb.go): StorageConfig + S3Config, GCSConfig, CloudFrontConfig, CloudCDNConfig, URLCacheConfig and their credential/encryption/multipart sub-messages, wired into Config as field 8. Backend is a oneof (s3 | gcs) with (buf.validate.oneof).required = true. CDN middleware (cloudfront | cloud_cdn) and url_cache are optional.
  • Delivery mode: instance-default delivery_mode enum (DELIVERY_MODE_REDIRECT / DELIVERY_MODE_PROXY) consumed by BlobStore.OpenBlob's two-axis resolution; DELIVERY_MODE_UNSPECIFIED (proto zero value) is rejected at startup via not_in: [0].
  • Runtime view + validation (internal/config/storage.go): proto → typed Go struct conversion with size/duration parsing, and Go-side cross-field validation at startup (single-backend required, CDN/backend pairing — CloudFront⇒S3, Cloud CDN⇒GCS — at-most-one-CDN, cloudfront.aws_region required when ip_filtered_by="awsregion", S3 chunk_size 5 MB–5 GB clamp, GCS 256 KiB alignment).

Why this shape

  • Cross-field invariants are enforced in Go at startup rather than in protovalidate, per the spec's "Configuration" section — they span sibling fields and parsed values that CEL cannot express cleanly.
  • The TLSConfig tls field was dropped from S3Config (field 20) and GCSConfig (field 10): the ported S3 v2 driver's TLS surface is fully expressed by tls_mode and the ported GCS driver has no TLS config, so the structured message had no consumer on either backend. Both tags are left reserved-in-comment; the shared TLSConfig message is retained for future consumers (Redis, HTTP upstreams, the HTTP listener).

References

  • Plan: docs/plans/2026-05-15-storage-layer.md — Step 10 (StorageConfig proto). Plan MR merged on main.
  • Spec: docs/specs/S06-storage-layer.md — "Configuration" section + acceptance criteria.
  • ADRs: ADR-005 (artifact delivery mode), ADR-013 (storage backend / CDN pairing), ADR-008 (content-addressable storage; root_directory default).

Tests

internal/config/storage_test.go — table-driven, run in go_unittests:

  • Happy-path load of S3 and GCS configs through config.Load (size→int64, duration→time.Duration, enum parsing), plus the absent-block no-op.
  • Validation rejections: missing backend, duplicate backend, CDN/backend mismatch, multiple CDN, missing cloudfront.aws_region, invalid duration/size strings, S3 chunk-size bounds, GCS chunk-size alignment, DELIVERY_MODE_UNSPECIFIED/unknown enum.

Acceptance

  • buf generate clean; YAML fixtures load through buf-protoyaml; validation rejects missing/double/mismatched backend, bad duration/size, and DELIVERY_MODE_UNSPECIFIED.

Related to #159 (closed)

🤖 Generated with Claude Code

Merge request reports

Loading
Loading