AWS SDK Go v2 s3 v1.100.0 defaults to RequestChecksumCalculationWhenSupported,
which causes PutObject to send STREAMING-UNSIGNED-PAYLOAD-TRAILER — a chunked
transfer mode R2 doesn't support. Setting WhenRequired makes the SDK send a
standard signed payload instead, resolving the 403 SignatureDoesNotMatch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds R2 (Cloudflare) as a direct upload target alongside B2 (cold archive).
When ACB_R2_* credentials are configured, the worker uploads replays and
thumbnails to R2 immediately after each match, bypassing the index-builder's
B2→R2 promotion cycle.
This is necessary because ARMOR's B2 app key is write-only; reads via the
direct S3 path return 403. The Cloudflare CDN read path (armor-hub-b2.ardenone.com)
is dead post-hub-decommission. Direct R2 upload ensures replays are available
without waiting for a working B2 read path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EndpointResolverV2 with a custom static URI does not honor UsePathStyle —
the resolver provides the final endpoint and the SDK does not re-apply
path-style bucket addressing on top of it. This means the bucket name was
dropped from the request path even with UsePathStyle=true, sending PUTs
to /replays/... instead of /armor-apexalgo/replays/...
BaseEndpoint is the SDK's documented approach for S3-compatible custom
endpoints; it sets the base URL and then correctly applies path-style
addressing to produce /bucket/key URLs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes:
1. Add UsePathStyle=true to B2 S3 client. Without it the SDK uses
virtual-hosted addressing, dropping the bucket name from the request
path. Uploads hit /replays/... instead of /armor-apexalgo/replays/...
causing NoSuchBucket errors on every replay/thumbnail PutObject.
2. Don't update crash_strikes for normal game endings (stalemate, turns).
In snake-style games every bot eventually crashes into a wall/snake —
that is the expected end condition, not an HTTP error. The old code
treated all Crashed[] entries from the engine as errors, causing all
6 bots to accumulate strikes after every single match and hitting the
30-min cooldown threshold after just 3 matches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The BaseEndpoint approach with older aws-sdk-go-v2 causes
"Invalid region: region was not a valid DNS name" errors when
uploading to ARMOR's S3-compatible endpoint.
Switching to EndpointResolverV2 bypasses the SDK's endpoint
rule validation entirely, resolving the issue.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fixes 'Invalid region: region was not a valid DNS name' error when
uploading replays to B2 via ARMOR proxy.
The error was caused by a known bug in aws-sdk-go-v2 v1.41.4 where
the endpoint resolver would validate the region as a DNS name even
when using a custom BaseEndpoint with UsePathStyle=true.
Upgraded SDK versions:
- github.com/aws/aws-sdk-go-v2 v1.41.4 -> v1.41.6
- github.com/aws/aws-sdk-go-v2/config v1.32.12 -> v1.32.16
- github.com/aws/aws-sdk-go-v2/service/s3 v1.97.2 -> v1.100.0
- github.com/aws/smithy-go v1.24.2 -> v1.25.1
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The AWS SDK requires a valid AWS region name even when using custom
S3-compatible endpoints (ARMOR/B2). Using "auto" as the region causes
an error: "Invalid region: region was not a valid DNS name."
This fixes the replay upload pipeline which was failing with the
invalid region error. Replays should now upload successfully to B2
via the ARMOR proxy.
Related to ai-code-battle-o43: Replay viewer verification task.
Remove custom endpoint resolver and use AWS SDK's standard approach
for S3-compatible endpoints:
- Use config.WithRegion("auto") for custom endpoints
- Set BaseEndpoint directly via s3.NewFromConfig options
- Add UsePathStyle for B2 compatibility
This fixes the 'Invalid region: region was not a valid DNS name' error
that was preventing replay uploads. The deployment manifest already
sets ACB_B2_REGION to empty string to avoid conflicts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Worker now gzip-compresses replays before uploading to B2 with
key replays/{match_id}.json.gz and Content-Encoding: gzip.
Updated B2 client Upload to accept contentEncoding parameter.
Fixed downstream web consumers (matches, bot-profile, playlists)
to reference .json.gz URLs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cleanup of superseded code that no longer matches the architecture:
Removed:
- worker-api/ - Cloudflare Worker with D1, superseded by K8s-based matchmaker + direct PostgreSQL
- cmd/acb-indexer/ - TypeScript index builder, superseded by Go cmd/acb-index-builder/
- cluster-configuration/ - K8s manifests belong in ardenone-cluster repo
Gutted cmd/acb-api/:
- Removed registration, job claim/result endpoints (deferred for v1)
- Removed dead code: predictions.go, seasons.go, series.go, register.go, jobs.go, glicko2.go
- API is now a stub with only health/ready endpoints
- Matchmaker and workers handle the core loop without it
Updated PROGRESS.md to reflect current architecture.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>