` for every route whose chain contains `cors:`, with a handler that answers the preflight from the profile. Boot error when such a route's path also matches the global `http.cors.paths` (the global wrapper would answer the preflight first).
+
+### Pattern 5: S3 build tag (D-13/D-14)
+```go
+// modules/lagoon/attach/s3_on.go
+//go:build summercms_s3
+package attach
+import _ "gocloud.dev/blob/s3blob"
+const s3Enabled = true
+
+// modules/lagoon/attach/s3_off.go
+//go:build !summercms_s3
+package attach
+const s3Enabled = false
+
+// in OpenBucket, before blob.OpenBucket:
+if strings.EqualFold(u.Scheme, "s3") && !s3Enabled {
+ return nil, fmt.Errorf("attach: storage.uploads.bucket_url uses s3:// but this binary was built without the summercms_s3 build tag; add it under build.tags in summer.yaml and rebuild (raw go build needs -tags summercms_s3)")
+}
+```
+`normalizeBucketURL` already passes unknown schemes through unchanged (`default: return raw, nil`) [VERIFIED: modules/lagoon/attach/bucket.go:42-57]. `summer build` runs `exec.CommandContext(ctx, "go", "build", "-o", binRel, ".")` [VERIFIED: internal/build/build.go:49]; add `-tags=` from the manifest. Recommended `summer.yaml` shape (discretion):
+```yaml
+build:
+ tags:
+ - summercms_s3
+```
+Validate each tag against `^[a-z0-9_]+$`. `SaveManifest` (used by `plugin:add`) rewrites only `module`, `binary` and `plugins` [VERIFIED: internal/build/manifest.go:90-103], so it **must** round-trip `build.tags` or `summer plugin:add` silently drops S3. `dev` watch reuses `build.App` [VERIFIED: internal/dev/watch.go:45-46], so it inherits the tags.
+
+S3 URL query (gocloud v0.46.0) [VERIFIED: gocloud.dev@v0.46.0/blob/s3blob/s3blob.go:118-136, aws/aws.go:70-180]: s3blob-specific `ssetype`, `kmskeyid`, `accelerate`, `use_path_style` (legacy `s3ForcePathStyle`) and `disable_https`; AWS config `region`, `endpoint`, `hostname_immutable`, `profile`, `role`, `anonymous`, `dualstack`, `fips`, `rate_limiter_capacity`, `request_checksum_calculation`, `response_checksum_validation` (`when_supported|when_required`). Unknown params are an error. Example for an S3-compatible endpoint:
+```
+s3://uploads?region=auto&endpoint=https://.r2.cloudflarestorage.com&use_path_style=true&request_checksum_calculation=when_required&response_checksum_validation=when_required
+```
+
+### Pattern 6: Disk-name extension helper (D-15)
+`Store` derives the disk name from the client extension: `diskName, err := newDiskName(ext)` [VERIFIED: modules/lagoon/attach/store.go:151] after sniffing `contentType := baseMediaType(http.DetectContentType(head))` [store.go:141]. The defaults already reject `x.html` (`DefaultFileExtensions` has no html, svg or xml [store.go:54-59]), so the regression test must widen `Limits.Extensions` (for example `[]string{"html", "png"}`). DetectContentType probe (Go 1.27) [VERIFIED: go run, this session]: png→`image/png`, docx/zip→`application/zip`, mkv→`video/webm`, mp3(ID3)→`audio/mpeg`, txt/ics/svg/`hello